• C#泛型详解


    C#泛型基础知识点总结

     

      1.0  什么是泛型

          泛型是C#2.0CLR(公共语言运行时)升级的一个新特性,泛型为.NET 框架引入了一个叫 type parameters(类型参数)的概念,type parameters 使得程序在设计的时候,不必设计其具体的参数,其具体的参数可以延迟到需要的时候声明或调用。使用泛型代码运行时避免了类型转换的装箱和拆箱操作。

      2.0 泛型的延迟声明:把参数类型的声明推迟到调用,不是语法糖,而是由框架升级提供的功能

    复制代码
     1  1 using System;
     2  2 using System.Collections.Generic;
     3  3 using System.Linq;
     4  4 using System.Text;
     5  5 using System.Threading.Tasks;
     6  6 
     7  7 namespace _20171010Generic
     8  8 {
     9  9     /// <summary>
    10 10     /// 泛型方法相关类
    11 11     /// </summary>
    12 12     public class GenericMethod
    13 13     {
    14 14         /// <summary>
    15 15         /// 泛型方法:方法带<>和type parameters(类型参数 T)的
    16 16         /// </summary>
    17 17         /// <typeparam name="T"></typeparam>
    18 18         /// <param name="tParameters"></param>
    19 19         public static void Show<T>(T tParameters)
    20 20         {
    21 21             Console.WriteLine("{0}方法,parameter={1}参数,type={2}类型", typeof(GenericMethod).Name, tParameters, tParameters.GetType().Name);
    22 22         }
    23 23     }
    24 24 }
    复制代码

    如代码所示,在声明泛型方法的时候没有指定具体的参数类型,等到需要调用的时候再指定,这就叫做延迟声明。泛型的设计思想(延迟思想,推迟一切可以推迟的)

    复制代码
     1  1 using System;
     2  2 using System.Collections.Generic;
     3  3 using System.Linq;
     4  4 using System.Text;
     5  5 using System.Threading.Tasks;
     6  6 
     7  7 namespace _20171010Generic
     8  8 {
     9  9     class Program
    10 10     {
    11 11         static void Main(string[] args)
    12 12         {
    13 13 
    14 14             int iValue = 123;
    15 15             string sValue = "TestName";
    16 16             DateTime dtValue = DateTime.Now;
    17 17             object oValue = new object();
    18 18 
    19 19             GenericMethod.Show(iValue);
    20 20             GenericMethod.Show(sValue);
    21 21             GenericMethod.Show(oValue);
    22 22             GenericMethod.Show(dtValue);
    23 23             Console.WriteLine("———————我是华丽的分割线————————");
    24 24             GenericMethod.Show<int>(iValue);
    25 25             GenericMethod.Show<string>(sValue);
    26 26             GenericMethod.Show<object>(oValue);
    27 27             GenericMethod.Show<DateTime>(dtValue);
    28 28            
    29 29             Console.WriteLine("———————我是华丽的分割线————————");
    30 30             Console.WriteLine(typeof(List<int>));
    31 31             Console.WriteLine(typeof(Dictionary<,>));
    32 32             Console.WriteLine("———————我是华丽的分割线————————");
    33 33         }
    34 34     }
    35 35 }
    复制代码

     泛型方法的调用,第一种  GenericMethod.Show(iValue);调用方法不指定类型参数,在编译的时候编译器自动编译推算(语法糖),第二种  GenericMethod.Show<int>(iValue);调用方法指定类型参数,类型参数和参数类型须一致,否则编译不通过。VS2017鼠标移上去会提示可以简化方法名称。编译的时候,类型参数编译为占位符,程序运行的时候,JIT(即时编译(Just In-Time compile)即时编译为真实类型。所以使用泛型性能会比使用object作为参数的方法好,(ps:经过测试)。 Console.WriteLine(typeof(List<int>)); 和Console.WriteLine(typeof(Dictionary<,>));的运行结果中有个~1,和~2就表示类型参数的占位符。

      3.0 泛型主要的四种:泛型类, 泛型方法,泛型接口,泛型委托  

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace _20171010Generic
    {
        /// <summary>
        /// 动物类
        /// </summary>
        public class AnimalModel
        {
            public int Id { getset; }
            public String Name { getset; }
            public virtual void Cry()
            { }
        }
        public interface IEat
        {
            void Eat();
        }
        public interface ISleep
        {
            void Sleep();
        }
     
        /// <summary>
        /// 狗类
        /// </summary>
        public class Dog:AnimalModel
        {
            public override void Cry()
            {
                Console.WriteLine("旺旺旺。。。。。");
            }
        }
        /// <summary>
        /// 猫类
        /// </summary>
        public class Cat : AnimalModel
        {
            public override void Cry()
            {
                Console.WriteLine("喵喵瞄。。。。。。。");
            }
        }
     
        /// <summary>
        /// 玫瑰花类
        /// </summary>
        public class Rose
        {
            public int Id { getset; }
            public string Name { getset; }
        }
     
    }

     首先先新建了一个AnimalModel类,里面定义了一个动物类,动物类里有个虚方法Cry,一个狗类,狗类继承了动物类,一个猫类,重写了虚方法Cry。一个IEat接口和ISleep接口,

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace _20171010Generic
     8 {
     9     /// <summary>
    10     /// 泛型类
    11     /// </summary>
    12     /// <typeparam name="T">类型参数</typeparam>
    13     /// <typeparam name="S">类型参数</typeparam>
    14     /// <typeparam name="K">类型参数</typeparam>
    15     public class GenericClass<T, S, K>
    16     {
    17         /// <summary>
    18         /// 无返回值的泛型方法
    19         /// </summary>
    20         /// <typeparam name="T"></typeparam>
    21         public void Show(T t)
    22         {
    23 
    24         }
    25         /// <summary>
    26         /// 有返回值的泛型方法
    27         /// </summary>
    28         /// <typeparam name="T"></typeparam>
    29         /// <returns></returns>
    30         public T Get()
    31         {
    32             return default(T);
    33         }
    34     }
    35 
    36     /// <summary>
    37     /// 泛型接口
    38     /// </summary>
    39     /// <typeparam name="W"></typeparam>
    40     public interface ISleep<W>
    41     {
    42         W Sleep(W t);
    43     }
    44 
    45     /// <summary>
    46     /// 有返回值的泛型委托
    47     /// </summary>
    48     /// <typeparam name="Y"></typeparam>
    49     /// <returns></returns>
    50     public delegate Y DlgYFun<Y>();
    51 
    52     public delegate int DlgIntFun();
    53 
    54     /// <summary>
    55     /// 泛型类
    56     /// </summary>
    57     /// <typeparam name="W"></typeparam>
    58     /// <typeparam name="Y"></typeparam>
    59     /// <typeparam name="M"></typeparam>
    60     public class GenericChild<T, S, K>
    61         //: GenericClass<T, S, K>直接继承泛型类
    62         //: GenericClass<T, S, string>//类型参数可直接指定
    63         //: ISleep<string>
    64         : ISleep<T>//实现泛型接口
    65     {
    66         T ISleep<T>.Sleep(T t)
    67         {
    68             return default(T);
    69         }
    70     }
    71 
    72     /// <summary>
    73     /// 普通类
    74     /// </summary>
    75     public class Child
    76        // :GenericClass<T,S,K>错误的继承,普通类不能直接继承泛型类
    77        //: GenericClass<string, int, double>//必须指定全部确定的类型参数后可继承泛型
    78        //:ISleep<W>错误的实现泛型接口,普通类不能直接实现泛型接口,
    79        : ISleep<string>
    80     {
    81         public string Sleep(string t)
    82         {
    83             Console.WriteLine("实现了sleep泛型接口,返回参数是:{0}", t);
    84             return t;
    85         }
    86     }
    87 }
    复制代码

    泛型类就在普通类名字后面加上<>和多个类型参数,需要注意的是 1.普通类不能直接继承泛型类和泛型接口,因为泛型的类型参数不确定,但是泛型类或泛型接口指定类型后可以继承泛型类或实现泛型接口,2.泛型类可以直接继承泛型类,也可以直接实现泛型接口,其子类的类型参数相当于声明了局部参数。

      4.0泛型的约束(基类约束,接口约束,引用类型约束,值类型约束,无参构造函数约束)

        回到上面写的那个GenericMethod类里的show方法,new 一个cat对象,Cat cat=new Cat(){ Id=1,Name="小黑猫"}; 然后调用genericMentod.show(cat)方法。但是如果想要在show方法里访问Id,或者Name却不行。T是个不明确类型参数,所以无法访问,如图

    使用泛型约束解决方法:

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace _20171010Generic
     8 {
     9     /// <summary>
    10     /// 泛型约束
    11     /// </summary>
    12     public class Constraint
    13     {
    14        
    15         public static void Show<T>(T tParameter)
    16             //where T: AnimalModel  基类约束,就可以访问该类的方法或属性
    17             where T:Cat //或者该子类       
    18         {
    19             Console.WriteLine("泛型约束show方法--------id={0},name={1}",tParameter.Id,tParameter.Name);
    20         }
    21 
    22         public static void Show(AnimalModel model)
    23         {
    24             Console.WriteLine("普通show方法--------id={0},name={1}", model.Id, model.Name);
    25         }
    26 
    27         public static void ShowInterface<T>(T tParameter)
    28            //where T: AnimalModel  基类约束,就可以访问该类的方法或属性
    29            where T : Cat,ISleep,IEat//或者该子类约束,多个接口约束 
    30             
    31         {
    32             Console.WriteLine("泛型约束ShowInterface方法--------id={0},name={1}", tParameter.Id, tParameter.Name);
    33             tParameter.Sleep();//接口的方法
    34             tParameter.Eat();
    35         }
    36     }
    37 
    复制代码
    Constraint类里的第一个show方法中在后面带个 where关键字 约束类型,泛型方法里就能访问Id和Name,第二个show方法是作为对比,虽然第二个方法也能实现同样的效果,但是相对泛型方法不灵活,泛型方法可以同时约束多个,比如第三个方法约束多个接口,和类,多个约束的关系是&&关系
    泛型约束除了基类约束和接口约束几种,还有值类型约束,无参构造约束,引用类型约束等这几种。
    基类约束:
    1带来权利,可以使用基类里面的属性和方法。
    2带来义务,类型参数必须是基类或者其子类。
    复制代码
    1  public static T TestFun<T>()
    2             // where T:class    //引用类型约束
    3             // where T:struct   //值类型约束
    4            where T : new()      //无参构造函数约束
    5         {
    6            T t = new T();
    7             return default(T);
    8         }
    复制代码

       5.0协变和逆变

     out 协变(covariant) 修饰返回值,in 逆变(contravariant) 修饰传入参数。out和in只能放在接口或者泛型委托的的参数前面,类没有协变和逆变。在.NET Framework里面,IEnumerable<T>转到定义去看,其实就是个带out参数的泛型接口,Action<T>转到定义去看就是个带in参数的泛型委托。还有一个逆变+协变的Func<T>

    像平常一样写代码:  AnimalModel animal = new AnimalModel();//实例化一个动物。

                       Dog dog = new Dog();//实例化一个条单身狗

              AnimalModel dog2 = new Dog();//实例化一条单身狗(狗继承了动物父类,父类出现的地方都可以用子类代替,对的,狗一定是个动物),左边父类,右边子类。

             // Dog dog3 = new AnimalModel();动物不一定是条单身狗,程序编译不通过

    new一条单身狗没问题,new 一群单身狗试试看。

            List<Dog> dogList = new List<Dog>();//实例化一群单身狗(编译通过)

            List<AnimalModel> animalDog = new List<Dog>();//实例化一群单身狗(语法上不通过)

    理论上来说第二种实例化一群狗的方式是没毛病的,一群狗也一定是一群动物,但是程序上是不通过是因为Listt<T>是个泛型 List<Dog>不是继承List<AnimalModel>,没有父子关系,程序只认关系。。。

    PS:写到这里我就快写不下去了,狗快被我自己玩坏了。

    要使上面那句代码编译通过,可以通过lambda表达式转化  List<AnimalModel> animalDog = new List<Dog>().Select(x => (AnimalModel)x).ToList();把每条狗都转换一遍

    使用IEnumerable:IEnumerable<AnimalModel> animalDog= new List<Dog>();   //这就叫协变。IEnumerable<out  T>在编译的时候就通过转化了,我个人理解为out 是表示转化后的T返回标识。平常在工作中,有用过out 关键字作为标识的返回参数,会用,但是不其所以然。原理明白后自己也可以定义一个协变的泛型接口。

    复制代码
     1  public interface IMyTest<out T>
     2     {
     3 
     4     }
     5     public class Test<T> : IMyTest<T>
     6     {
     7 
     8     }
     9 
    10 
    11    IMyTest<Animal> test3 = new Test<Dog>();
    复制代码

    逆变就和协变相反。逆变的in 的参数只能作为传入值,不能作为返回值。说白了,也是一种约束。协变和逆变的关键作用就是让编译器在运行时不报错。

    复制代码
     1  1  public interface IMyTest<inT>
     2  2     {
     3  3 
     4  4     }
     5  5     public class Test<T> : IMyTest<T>
     6  6     {
     7  7 
     8  8     }
     9  9 
    10 10 
    11 11    IMyTest<Dog> test3 = new Test<Animal>();
    复制代码

    为什么要用泛型:泛型就是为了满足不同类型,相同代码的重用 

    关于泛型的知识点还有很多,比如还有泛型的缓存,这个就有点难理解了。以上知识点是我平常通过各种途径学习总结的几点。如有不对欢迎指正。欢迎转载和分享,转载分享时请注明原创出处:如此拉风的女人

  • 相关阅读:
    wpf如何将图片设置为窗体的背景
    C#用Oracle.DataAccess中连接Oracle要注意版本问题!
    C#格式化数值结果表
    将字符串的16进制转换成byte[]
    Java各个类型与byte[]的转换
    记录下 Jquery的使用
    页面div与顶部有缝隙问题
    Js 转换Json返回的时间格式(转)
    Div垂直居中水平居中
    C# 日期格式大全
  • 原文地址:https://www.cnblogs.com/qiu18359243869/p/9493486.html
Copyright © 2020-2023  润新知