• 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);

     

     

     

     

  • 相关阅读:
    java 上传图片
    getElementById 鼠标经过字体改变颜色
    getElementById 学习
    css的绝对定位与相对定位
    关于For循环
    扩展方法的应用
    关于Function 的学习笔记
    <a>标签中查找文件的方法
    关于Name ID class属性的区别
    使用float设置经典的网站前端结构
  • 原文地址:https://www.cnblogs.com/lisong-home/p/7883163.html
Copyright © 2020-2023  润新知