Net学习日记_泛型与反射_笔记

一、泛型

为什么要有泛型集合(List<T>,Dictionary<K,V>,LinkedList<T>)?

1.为了避免装箱拆箱;

2.复用集合类里的 代码(算法) List<string> List<int>

1.概念

1.1官方:是一种特殊的【算法重用】机制。允许程序员在代码中将 变量或参数的类型 先用【类型占位符】来代替,等到运行的时候再根据传入的【类】来替换。

通俗:类也可以带参数了!但是这个参数必须是 类型!用来在 类的代码中 暂时代替 类的位置,然后会在运行时,被替换。

public class MyList<T>
{
    T[] arr;
    public MyList(T[] arrp)
    {
        arr = arrp;
    }
}

2.语法

2.1泛型的运行

当 运行的时候,JIT会帮我们生成泛型类的不同版本,通过如下代码可以验证:

//aa是MyList中一个int的静态变量
MyList<string>.aa = 11;
MyList<int>.aa = 22;
//照理说,同为MyList类的静态变量,应该保存最后一次赋值的 22
//但是输出的时候,确实生成了两个不同的版本
Console.WriteLine(MyList<string>.aa.ToString());//11
Console.WriteLine(MyList<int>.aa.ToString());//22
//由此看出,JIT在运行代码的时候,分别为带不同的 泛型参数 的MyList类生成了不同的版本。 


MyList<string>.dd = new Dog();
MyList<string>.dd.age = 11;

MyList<int>.dd = new Dog();
MyList<int>.dd.age = 22;

Console.WriteLine(MyList<string>.dd.age.ToString());//11
Console.WriteLine(MyList<int>.dd.age.ToString());//22

【问题】为什么JIT在执行的是有要根据泛型类 MyList<T>里的T 来产生不同的 MyList类的版本?

因为 只有当T确定为某种类型的时候,JIT才有可能计算出 当前MyList类的对象在内存中要分配多大的空间。

3.泛型约束

3.1基类约束

public class House<TPet,TPet2> 
    where TPet:Dog   //约束 TPet必须是 Dog或Dog的子类
    where TPet2:Cat  //约束 TPet必须是 Cat或Cat的子类
{
    TPet pet1;
    TPet2 pet2;
}

测试:

//报错,因为string 不是 Dog或它的子类
House<string, Cat> myHouse = new House<string, Cat>();
//成功,因为 DogMini 继承于 Dog
House<DogMini, Cat> myHouse2 = new House<DogMini, Cat>();

注意:2个禁忌

public class House<TPet,TPet2>
    where TPet : Nullable<int>//错:不能约束 为 Nullable 的子类,因为语法有歧义
    where TPet2:string//错:不能约束为 密封类的 子类,因为不存在子类!

3.2接口约束

interface IGetReward<T>
{ T GetReward(); }

interface IWalk
{ void Walk(); }

interface ISing<T>
{ T Sing(); }

class MyPetPlay<T, V> where T : IGetReward<T>   where V : IWalk, ISing<V>
{
       public MyClass(T t,V v)
      {
            t.GetReward(); //直接调用接口方法
            v.Walk();
            v.Sing();
      }
}

3.3 struct/class约束

public struct A { }
public class B { }

public class C<T> where T : struct //T 只能是 值类型
{
}
public class C2<T> where T : class //T 只能是 引用类型
{
}
测试:
C<int> c1 =new C<int>();
C<Dog> c2 =new C<Dog>();//异常
C2<int> c3 =new C2<int>();//异常
C2<Dog> c4=new C2<Dog>();

3.4构造器约束

约束 泛型参数 的实参必须 是一个 有无参构造函数 的类。

注意:C#现在只支持 无参构造函数 约束。

class Dog
{
    public Dog(){ }
}
class Cat
{
    public Cat(int age){ }
}
class Pet<T> where T : new()
{
    T t;
    public Pet()
    {
        t = new T();
    }
}
public static void Main(string[]arg)
{
        Pet<Dog> c = new Pet<Dog>();
        Pet<Cat> d = new Pet<Cat>();//异常,因为Cat没有无参构造函数
}

4.泛型方法

4.1概念:

即使所在类不是泛型类,或者方法的泛型参数不再泛型类的参数列表里,也依然使用泛型的方法。

/// <summary>
/// 自定义转型方法
/// </summary>
/// <typeparam name="T">被转集合的元素类型</typeparam>
/// <typeparam name="R">要转成的集合的元素类型</typeparam>
/// <param name="list">待转换的集合</param>
/// <returns>转换后的结果集合</returns>
public List<R> SelfCast<T, R>(List<T> list) 
    where R:class //规定R必须是引用类型
{
    //创建一个要返回的 集合
    List<R> resList = new List<R>();
    //遍历 要转换的集合元素
    for (int i = 0; i < list.Count; i++)
    {
        //取出元素
        T t = list[i];
        //转换成 指定类型
        R r = t as R; //as  只能转换 引用类型对象 所以 方法定义了 where R:class
        //加入 到 要返回的集合中
        resList.Add(r);
    }
    return resList;
}

二、反射

1.概念

反射机制是一种运行时获取类(Type对象)和动态调用对象的成员的机制。

a.可以获取有关已加载的程序集和在其中定义的类型(如类、接口和值类型)的成员信息;

b.可以使用反射在运行时创建指定类的对象,以及调用和访问这些对象的成员。

这种动态获取的信息以及动态调用对象的方法的功能称为反射机制。

2.动态获取类的成员

2.1Type类

概念:每个类(class)在内存中都有一个对应的Type类对象,class中定义的成员信息都存在这个Type类对象中。

通俗:程序员写的类,在运行时也会变成一个对象,Type的对象。

Dog d1 = new Dog();
Dog d2 = new Dog();
//MessageBox.Show((d1==d2).ToString());//false

//通过 对象 获取对象所属 的 【类的对象】
Type t1 = d1.GetType();
Type t2 = d2.GetType();
MessageBox.Show((t1==t2).ToString());//true 两个Dog对象的Type都是Dog类

JIT生成Type类的对象:只有在程序中第一次遇到某个类的时候,才会去加载这个类,并解析类的内容,然后生成对应的Type对象,并将类解析出来的信息装入Type对象中存入内存,方便以后复用。

2.1.1获取方式:

a. 通过对象的GetType()方法: 【Object类中有个 GetType()实例方法】

Dog dog = new Dog();
Type type= dog.GetType();//获取了Dog类的Type对象
Dog dog2 = new Dog();
Type type2= dog2.GetType();//获取了Dog类的Type对象
Console.WriteLine(type==type2);//true

特点:必须先创建对象,才能获取对象对应类的类型对象

 

b.通过typeof关键字+类

Type type3 = typeof(Dog);
Console.WriteLine(type==type3);//true

特点:只要获取类,就能获取类的类型对象

 

c.通过当前运行的程序集获取 + 类全名称字符串

Type type5 = Assembly.GetExecutingAssembly().GetType("MyNameSpace.Dog");

特点:通过类的全名称字符串即可获取某个程序集里的对应类型对象。

 

2.1.2根据Type创建对象
//使用激活器对象Activator (相当于:Dog d6 =new Dog();)
Dog dog6 = Activator.CreateInstance(type) as Dog;
//注意:默认调用了 对应类的 无参构造函数
//在创建对象时根据构造函数参数来调用指定的构造函数
Dog dog7 = Activator.CreateInstance(type, "德国牧羊犬", "2") as Dog;

注意:Activator.CreateInstance(t) 虽然会按照t的类型创建一个对象的对象,但都是以object返回,如需使用,需要转成 对应的类型。

2.1.3根据Type获得类的字段
2.1.4根据Type获取类的方法和属性

Type类的常见属性:

Assembly:获取该Type对应类所在的程序集对象。

FullName:Type对应类的全名称【命名空间名+类名】

GenericTypeArguments:该类的类型参数(泛型参数)数组

IsAbstract:是否是抽象类

IsClass:是否是一个类,而不是接口或值类型

IsGenericType:是否是泛型类

IsInterface:是否是接口

Type类的常见方法:

3.Assembly 程序集类

3.1概念

就是 程序集文件 加载到 内存里后 的对象。里面包含了各种方法,可以获取程序中的各种类型。

3.2Assembly.GetTypes()获取到的类型数组信息:

3.3获取Assembly的方式

3.3.1通过 Assembly的 GetExecutingAssembly 方法,获取正在执行的 程序集对象。

Assembly ass = Assembly.GetExecutingAssembly();

补充:

添加引用的真实作用:

1.修改了项目的工程文件.csproj(配置文件),在里面添加了被引用的项目所在的位置。

2.那么编译器在生成 宿主程序的时候,会先编译生成 引用的项目,如果引用的项目的程序集生成成功,则把程序集文件 复制到 宿主程序的 bin目录下;

3.在宿主程序中可以直接访问 被引用的项目 里的命名空间和类了!(其实是在运行的时候,宿主程序主动将被引用程序加到了 宿主程序的应用程序域中了。)

3.3.2通过Type.Assembly获取Type所在的程序集对象。

MODEL.Cat cat = new MODEL.Cat();
Type catType = cat.GetType();
Assembly assModel = catType.Assembly; 

3.3.3通过程序集文件路径 加载Assembly

Assembly ass3 = Assembly.LoadFrom(@"F:DALinDebugDAL.dll");

 

4.反射应用

4.1判断类是否实现某个接口

Type对象的 IsAssignableFrom方法

private void btnInterfact_Click(object sender, EventArgs e)
{
    Type typeCat = typeof(Cat);
    Type typeString = typeof(String);
    Type typeIBark = typeof(IBark);

    Console.WriteLine(typeIBark.IsAssignableFrom(typeCat));//true
    Console.WriteLine(typeIBark.IsAssignableFrom(typeString));//false
}

4.2动态调用方法

//获取类名
string strClassName = System.Configuration.ConfigurationManager.AppSettings["type"];
//根据类名创建 类型对象
Type typeCat = Assembly.GetExecutingAssembly().GetType(strClassName);
//获取要调用的方法名称
string strMethod = System.Configuration.ConfigurationManager.AppSettings["methodName"];
//获取 一个方法对象
MethodInfo method = typeCat.GetMethod(strMethod);
//创建 了一个 指定的对象
object objCat= Activator.CreateInstance(typeCat);
//调用方法 并为方法传入一个对象(objCat)作为 方法的作用域,最终获取方法的返回值
string returnValue = method.Invoke(objCat,null).ToString();
MessageBox.Show(returnValue);

补充:为什么使用配置文件?

因为配置文件可以不编译到 程序集中,随时可以进行方便的修改,程序集在运行的时候可以读取配置文件里配置的信息,从而影响程序内部的运行。

 

复习序列化

using System.Runtime.Serialization.Formatters.Binary;

1.序列化一个对象,将对象中的重要信息转成二进制数据,并提供给指定的流用来操作。

Dog dog = new Dog("瑞奇", 1);
using (FileStream fs = new FileStream(@"F:广州传智.Net上课视频DotNet03三期Day2013-02-02-反射-Asp.netCodeAspNet1序列化inDebugdog.bin", FileMode.OpenOrCreate))
{
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(fs, dog);
    MessageBox.Show("序列化成功~~!");
}

序列化时:

将对象所对应的类所在程序集的名字 记录,将类的全名称记录,将对象里的 每个 字段 的名字和值记录;

并将这三个数据转成二进制数据存入 对应问流。

 

将对象转成byte数组

using (MemoryStream ms = new MemoryStream())
{
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(ms, dog);
    MessageBox.Show("序列化成功~~!");
    byte[] arr = ms.ToArray();
}

2.反序列化:将被序列化对象所产生的二进制数据获取,并根据二进制数据里的 类名 new一个新的对象,将二进制数据里保存的字段的值,设置给新对象对应的字段(真个反序列化的过程都是通过反射实现的)

using (FileStream fs = new FileStream(@"F:广州传智.Net上课视频DotNet03三期Day2013-02-02-反射-Asp.netCodeAspNet1序列化inDebugdog.bin", FileMode.Open))
{
    BinaryFormatter bf = new BinaryFormatter();
    Dog dog = bf.Deserialize(fs) as Dog;
    MessageBox.Show("反序列化成功~~!" + dog.age + "," + dog.name);
}

特性

概念:Attribute用来对类、属性、方法等标注额外的信息,贴一个标签(附着物)

通俗:就是为类 贴附加的标签。

语法:

public class CNameAttribute : Attribute //一定要继承 Attribute
{
    string strCName = string.Empty;
    /// <summary>
    /// 获取中文名称
    /// </summary>
    public string StrCName
    {
        get { return strCName; }
        set { strCName = value; }
    }

    public CNameAttribute()
    {
    }

    public CNameAttribute(string strCName)
    {
        this.strCName = strCName;
    }
}

为类和类的成员 添加 特性(标签),凡是"贴标签"就new 了一个 特性对象!设置给了 被贴 的对象

  [特性.CName]
    public class Classes
    {
        [特性.CName]
        public void Test()
        { 
        }

        [特性.CName]
        /// <summary>
        /// 班级表ID
        /// </summary>        
        public int? CID
        {
            set { _cID = value; }
            get { return _cID; }
        }

        /// <summary>
        /// 班级名称
        /// </summary>
        [特性.CName]
        public string CName
        {
            set { _cName = value; }
            get { return _cName; }
        }
    }

【源码】:可见 每个特性标签 都被实例化成了对象 存放在目标对象中

为特性的构造函数赋值:

其实,是省略了 小括号

使用命名参数的方式 为构造函数的参数赋值!

[特性.CName(StrCName="中文名称,哈哈哈!")]
public string CName
{
    set { _cName = value; }
    get { return _cName; }
}
[特性.CName("中文名称,哈哈哈哈")]
public string CName
{
    set { _cName = value; }
    get { return _cName; }
}

再次强调,我们在为类或类的成员 贴“标签”的时候,其实是为该类 的Type对象里 对应的成员对象(属性/方法...对象) 内部 添加了一个 “标签”对象。

注意:特性对象 是加在  Type对象中的,在类的实例中没有存在。

特性的使用:

1.判断成员是否加了特性 IsDefined

//获取 Classes 类型对象
Type t = typeof(MODEL.Classes);
//获取所有属性对象
PropertyInfo[] pros = t.GetProperties();

//获取 CNameAttribute特性 类型对象
Type tAttr = typeof(CNameAttribute);
//遍历属性对象
foreach (PropertyInfo pi in pros)
{
    //1.判断属性是否 有 贴 CNameAttribute 特性
    if (pi.IsDefined(tAttr, false))
    {
        Console.WriteLine(pi.Name);
    }
}

//2.判断类是否贴了 CNameAttribute特性
bool isCA= t.IsDefined(tAttr, false);

//3.判断 方法 是否贴了 CNameAttribute特性
MethodInfo mi = t.GetMethod("Test");
bool isMA = mi.IsDefined(tAttr, false);

 

 

 

 

原文地址:https://www.cnblogs.com/lisong-home/p/7883163.html