c#泛型的理解

1.什么是泛型:

    泛型就是不确定类型的,被调用时才指定类型的数据类型。

2.如何声明、使用泛型、泛型的好处:

    

 

 

如图所示:我们写了四个不相同类型的方法并且调用,但是我们每个不一样的类型都要重新多写一个方法就会很麻烦,根据 在c#中"object是所有类的基类"这句话 和里氏替换原则:子类可以替代父类并且出现在父类能出现的地方,我们可以得出下图案例:

如图所示:这不是就是相当解决我们的上面的问题了吗。但是这样写也会存在一些问题。

                  1.性能问题(装箱和拆箱),其实就是类型转换对性能的损耗

                  2.类型不安全问题

 在这里我写了三个方法分别是普通方法,obj方法,泛型方法,为了公平起见我里面没有调用任何东西,分别循环一亿次来查看时间。

 如上图所示:普通方法用的时间是347毫秒,obj方法用的是1071毫秒,泛型方法用的是305毫秒,obj方法的性能要比泛型方法差很多。

为什么呢?这就涉及到了装箱和拆箱,即obj方法在调用的时候要转换类型,(byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。string,类等皆为引用类型。),装箱的操作会损耗性能。而泛型方法是在调用的时候才确定类型转换成普通方法,普通方法性能好是必然的。

Model类:

 public interface ISports
    {
        void Pingpang();
    }

    public interface IWork
    {
        void Work();
    }


    public class People
    {
      

        public int Id { get; set; }
        public string Name { get; set; }

        public void Hi()
        { }

    }

    public class Chinese : People, ISports, IWork
    {
        public void Tradition()
        {
            Console.WriteLine("仁义礼智信,温良恭俭让");
        }
        public void SayHi()
        {
            Console.WriteLine("吃了么?");
        }

        public void Pingpang()
        {
            Console.WriteLine("打乒乓球...");
        }

        public void Work()
        {
            throw new NotImplementedException();
        }
    }

    public class Hubei : Chinese
    {
        //public Hubei(int i)
        //{ }

        public string Changjiang { get; set; }

        public void Majiang()
        {
            Console.WriteLine($"打麻将啦。。");
        }
    }


    public class Japanese : ISports
    {
        public int Id { get; set; }
        public string Name { get; set; }


        public void Pingpang()
        {
            Console.WriteLine($"打乒乓球...");
        }
    }

GenericConstraint类:

  public static void ShowObject(object oParameter)
        {
            //Console.WriteLine("This is {0},parameter={1},type={2}",
            //    typeof(CommonMethod), oParameter.GetType().Name, oParameter); 
            //Console.WriteLine($"People.Id={oParameter.Id}");
            People people = (People)oParameter;
            Console.WriteLine($"People.Id={people.Id}");
            Console.WriteLine($"People.Name={people.Name}");
        }

Program:

try{   
  {
                    
                    People people = new People()
                    {
                        Id = 123,
                        Name = "You"
                    };
                    Chinese chinese = new Chinese()
                    {
                        Id = 234,
                        Name = "7z"
                    };
                    Hubei hubei = new Hubei()
                    {
                        Id = 345,
                        Name = "晴天橙子"
                    };
                    Japanese japanese = new Japanese()
                    {
                        Id = 456,
                        Name = "苍老师"
                    };

                    GenericConstraint.ShowObject(people);
                    GenericConstraint.ShowObject(chinese);
                    GenericConstraint.ShowObject(hubei);
                    GenericConstraint.ShowObject(japanese);

                   
                }
            }
            catch (Exception ex)
            {

                throw;
            }

  

 

 

 

 从这里可以看出到了参数为 japanese的时候会报错。为什么呢?因为 Chinese继承于people,hubei继承于Chinese但是japanese继承于ISports ,在GenericConstraint类中是进行一个转换的,转成people,不然会报错,因为obj类型没有id和name,这里就不直接写出来了。这就是类型冲突,不匹配,不安全。

 

 

3.泛型的原理:

 

如上图所示:写入c#泛型语法→右键重新生成→就会生成这两个文件,也有可能是其中一个→运行时的二次编译,也就是这个时候确定泛型类型进而生成普通方法→转换成计算机能够识别的二进制编码。

那么T在声明时候经过编译是什么样子的呢?

 

如上图所示:T在声明的时候回形成占位符~  List传参位数是1就是~1  dictionart是~2,最终生成普通方法。

 

 

 

4.泛型的延迟声明:

 从上图可以看出泛型是在被调用的时候才确定参数类型的;延迟声明:在开发中,延迟一切能够延迟的处理,能晚点做,就尽量晚点做。

 

 

5.泛型类、方法、接口、委托

泛型方法:一个方法满足了不同类型的需求

泛型类:一个类满足不同类的需求

泛型接口:一个接口满足不同接口的需求

泛型委托:一个委托满足不同委托的需求

 

6.泛型约束,泛型缓存

举个例子:

 

 

 最终得出结果:

由图可以看出在第一次循环的时候都会进入构造函数,后续则不会,这是因为每次泛型根据被调用的参数类型都会拷贝一份、生成新的副本、类,所以每次都会进入构造函数里面,后续则因为已经拷贝则不在进入构造函数,这些拷贝的副本、类叫做泛型缓存,泛型缓存的本质就是泛型类。

 

在第二点的时候我们说到了obj类型不安全的问题,下面用泛型约束来解决这个问题:

GenericConstraint类:

   public static void Show<T>(T tParameter)//基类约束
            where T : People 
      
        { 
       

            {
                ////People people = (People)oParameter;
                Console.WriteLine($"People.Id={tParameter.Id}");//调用字段
                Console.WriteLine($"People.Name={tParameter.Name}");
                tParameter.Hi();//调用方法
} }
public static void IShow<T>(T tParameter) where T : ISports  //接口约束
{ 
tParameter.Pingpang();//调用接口里面的方法
}
public static void NShow<T>(T tParameter) where T : new()//无参数构造函数约束
{ 
//在Model中的Hubei类加入 public Hubei(int i){ } 一个有参构造函数
}

    //在编写之前要把上面的Hubei类的有参构造函数注释或者删掉

   /// <summary>
   /// 值类型约束
   /// </summary>
   /// <typeparam name="T"></typeparam>
   /// <param name="tParameter"></param>
   public static void ShowStruct<T>(T tParameter) where T : struct

    {

     }

   /// <summary>
   ///引用类型约束
   /// </summary>
   /// <typeparam name="T"></typeparam>
   /// <param name="tParameter"></param>
   public static T ShowClass<T>(T tParameter)   where T : class
   {
   return default(T); //任何类型都可以,default(T) 根据实际的调用类来生成默认值;
   }

 

   //也可以有多重约束如:

    

     public static void ShowInfo<T>(T tParameter)
     where T : People,ISports   //只要不冲突即可。

     {

      }

 





  

 

 Program:

//基类约束
GenericConstraint.Show<People>(people); GenericConstraint.Show(chinese); GenericConstraint.Show(hubei); //GenericConstraint.Show(japanese);//这里会出错,因为在GenericConstraint.show中使用了基类约束People,所以会导致类型不一致,禁止传入错误参数。

//接口约束
//GenericConstraint.IShow<People>(people);//这里会出错,因为people与ISports类型冲突,没有继承关系。
GenericConstraint.IShow(chinese);
GenericConstraint.IShow(hubei);
GenericConstraint.IShow(japanese);

//无参构造函数约束
GenericConstraint.NShow<People>(people);
GenericConstraint.NShow(chinese);
//GenericConstraint.NShow(hubei);//这里会报错,因为他有一个有参构造函数,类在声明的时候即使不去写也默认有一个无参构造函数,这就是另外几个类能传入的原因。
GenericConstraint.NShow(japanese);

//值类型约束

//下面都会出错,因为只能传入值类型,(byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。string,类等皆为引用类型。)。
GenericConstraint.NShow<People>(people);
GenericConstraint.NShow(chinese);
GenericConstraint.NShow(hubei);
GenericConstraint.NShow(japanese);


//引用类型约束

//下面皆正常,因为(byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。string,类等皆为引用类型。)。
GenericConstraint.NShow<People>(people);
GenericConstraint.NShow(chinese);
GenericConstraint.NShow(hubei);
GenericConstraint.NShow(japanese);










 

约束总结:(根据上面的代码来理解)

1.基类约束:约束这个T 就是People; 就约束只能传递People或者People的子类
2.接口约束:约束这个T 是ISports接口的实现类
可以调用接口中的方法---权利;
调用的时候--只能传递实现过这个接口的类进入--义务
 3.无参数构造函数约束:传入的参数必须有拥有一个无参数构造函数
4.值类型约束:只能传入值类型的参数
5.引用类型约束:只能传入引用类型的参数

 

 

 

7.协变逆变

ContravariantCovariance类:

  1  /// <summary> 
  2     /// out 协变covariant    修饰返回值 
  3     /// in  逆变contravariant  修饰传入参数
  4     /// </summary>
  5     public class ContravariantCovariance
  6     {
  7         public static void Show()
  8         {
  9             {
 10                 Animal animal1 = null;
 11                 animal1 = new Animal();
 12 
 13                 ///任何父类出现的地方都可以用子类来代替
 14                 Animal animal2 = null;
 15                 animal2 = new Cat();  
 16                  
 17                 Cat cat1 = null;
 18                 cat1 = new Cat();
 19 
 20                 //Cat cat2 = null;
 21                 //cat2 = new Animal();  //Animal不一定等于cat,不严密,存在风险,会出错
 22 
 23                 //Cat cat2 = null;
 24                 //cat2 = (Cat)(new Animal());  //  类型不安全,暂时不会出错,但是类型不安全,容易出错,不严密
 25             }
 26 
 27             {
 28 
 29                 List<Animal> animalList1 = null;
 30                 animalList1 = new List<Animal>();
 31 
 32                 //泛型类在我们指定了类型以后,其实是一个新的类,/List<Animal>与List<Cat>()并没有继承关系,所以无法转换类型,会出错
 33                 //List<Animal> animalList2 = null;
 34                 //animalList2 = new List<Cat>();    //一组Cat 一定是一组Animal,口语上的理解和代码冲突了;
 35 
 36                 //为什么不能这样写?  是因为是两个不同的类,也没有继承关系; 在C# 语法中,只能用父类声明子类的实例
 37                 //泛型存在不和谐的地方; 
 38                 //在这类场景下,编译器也有不完美的地方---编译器错了; 
 39 
 40             }
 41              
 42             //就引入了协变和逆变
 43             { 
 44                 //协变  就可以让右边用子类
 45                 IEnumerable<Animal> animalList1 = new List<Animal>();
 46                 IEnumerable<Animal> animalList2 = new List<Cat>(); //这个可以 ,相当于解决了刚刚编译器不完美的点
 47 
 48                 //这样写,才符合常理; 
 49                 //有没有其他别的问题?
 50                 //会存在风险;
 51 
 52     
 53                  
 54                 //协变: Out 只能做返回值 ,不能做参数,其实是一种为了避开风险而存在的一种约束
 55                 ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
 56                 ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();  //协变 
 57                 //customerList2.Show(new Animal()); //这个还能玩吗? 当然不行;
 58                 //customerList2.Show(new Cat());
 59 
 60 
 61             }
 62             {//逆变 In  只能做参数 ,不能做返回值,其实是一种为了避开风险而存在的一种约束
 63 
 64                //逆变:就可以让右边用父类;
 65                 ICustomerListIn<Cat> customerList2 = new CustomerListIn<Cat>();
 66                 //ICustomerListIn<Cat> customerList1 = new CustomerListIn<Animal>();
 67 
 68                 //customerList1.Get();//调用的是接口的方法
 69 
 70                 //customerList1.Get();  //返回的一定是一个Cat 或者是Cat 的子类;
 71                 //因为通过接口在调用方法的时候只能返回一个Cat,
 72             }
 73             //协变逆变的存在,就是为了满足常规场景添加一个避开风险的约束; 
 74             {
 75                 IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>();
 76                 IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();//协变
 77                
 78                 IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();//逆变 
 79                 IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();//协变+逆变
 80             }
 81 
 82             //协变和逆变 只有在泛型接口和泛型委托中存在;
 83 
 84             //协变 协变: Out 只能做返回值 ,不能做参数,可以让右边使用子类
 85             //逆变 逆变: in 只能做参数 ,不能做返回值,可以让右边使用父类
 86             //可以毫无顾忌的  去声明;
 87         }
 88         //协变是为了约束,不让输入父类。逆变是为了约束,不让返回父类!是这样理解吧!为了安全
 89 
 90         //架构师的层面来谈这个问题: 可能会出现问题地方,让你在使用的时候就直接编译不过;而不是口头约束;
 91     }
 92 
 93     /// <summary>
 94     /// 动物
 95     /// </summary>
 96     public class Animal
 97     {
 98         public int Id { get; set; }
 99     }
100 
101     /// <summary>
102     /// Cat 猫
103     /// </summary>
104     public class Cat : Animal
105     {
106         public string Name { get; set; }
107     }
108 
109     /// <summary>
110     /// T 就只能做参数  不能做返回值
111     /// </summary>
112     /// <typeparam name="T"></typeparam>
113     public interface ICustomerListIn<in T>
114     {
115        //T Get();
116 
117         void Show(T t);
118     }
119 
120     public class CustomerListIn<T> : ICustomerListIn<T>
121     {
122         //public T Get()
123         //{
124         //    return default(T);
125         //}
126 
127         public void Show(T t)
128         {
129 
130         }
131     }
132 
133     /// <summary>
134     /// out 协变 只能是返回结果 
135     /// 泛型T 就只能做返回值; 不能做参数; 
136     /// </summary>
137     /// <typeparam name="T"></typeparam>
138     public interface ICustomerListOut<out T>
139     {
140         T Get();
141 
142        //void Show(T t); 
143     }
144 
145     public class CustomerListOut<T> : ICustomerListOut<T>
146     {
147         public T Get()
148         {
149             return default(T);
150         }
151 
152         //public void Show(T t)
153         //{
154 
155         //}
156     }
157 
158     public interface IMyList<in inT, out outT>
159     {
160         void Show(inT t);
161         outT Get();
162         outT Do(inT t);
163 
164         ////out 只能是返回值   in只能是参数
165         //void Show1(outT t);//左边声明是父类---右边实例化是子类 new MyList<Cat, Cat>()---outT是cat--方法调用时以左边为准,传递的是animal的子类-狗
166         //inT Get1(); 
167     }
168 
169     /// <summary>
170     /// out 协变 只能是返回结果 
171     /// in  逆变 只能是参数
172     /// 
173     /// </summary>
174     /// <typeparam name="T1"></typeparam>
175     /// <typeparam name="T2"></typeparam>
176 
177     public class MyList<T1, T2> : IMyList<T1, T2>
178     {
179         public void Show(T1 t)
180         {
181             Console.WriteLine(t.GetType().Name);
182         }
183 
184         public T2 Get()
185         {
186             Console.WriteLine(typeof(T2).Name);
187             return default(T2);
188         }
189 
190         public T2 Do(T1 t)
191         {
192             Console.WriteLine(t.GetType().Name);
193             Console.WriteLine(typeof(T2).Name);
194             return default(T2);
195         }
196     }

协变逆变的理解基于一句话: 子类可以代替父类并且出现在父类能够出现的地方,但是父类并不能够代替子类。简而言之就是类型冲突 子类与父类。

 

 

      

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-07-29 20:49  超拽  阅读(670)  评论(0编辑  收藏  举报