泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能,泛型的引入可以使类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法。通过使用泛型类型参数T,您可以编写客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本和风险。– MSDN

实例

  • 打印Int类型,string类型,DateTime类型的值

    很简单,写了三个方法,分别打印各自的类型(除了参数类型不同,方法名称不同,其他都一样)。如果需要打印其他类型,我们同样继续创建类似方法即可,也不是什么难事。

    但是,我们会发现,这么写的话,实在是太费劲了,明明只是参数类型不同,却写了不同的方法。那在泛型没推出之前,有没有好的解决办法呢?当然有,神一般的程序员们,发明了下面写法:

    只需要一个方法,就可以打印不同参数类型的数值。思路肯定是没错的,但是这样会带来一个问题,在调用时会发生装箱操作,我们用ILSpy查看一下

    这么一来,如果频繁调用此方法,会有性能方面的影响。

    微软为解决这类问题,在C#2.0推出了泛型,泛型从此登上了历史舞台,以星火燎原之势占据了半壁江山。

List<T>

  • 常用的泛型List<T>

    写过C#的猿们肯定用过List<T>这个神奇的类,即便你还不知道这就是个泛型。

    T代表一个参数类型,在我们调用的时候指定具体的类型。如:List<int>,List<string>,List<DateTime>

泛型方法

  • 修改ShowObject方法为泛型方法

    泛型方法在方法名后面加一个“<>”,里面可以传入多个不同的参数类型。在本方法里面我们只用到一个参数类型(通常用大写的T代表),想想我们经常用的字典Dictionary<TKey, TValue>(泛型类),它不就有两个参数类型吗。现在我们再用ILSpy工具查看一下IL语言。

    从IL语言我们看到泛型并没有装箱操作,从而解决了由装箱操作带来的性能问题。泛型是如何解决的呢?

    我们看Show<T>方法,并没有事先声明好具体的参数类型,在调用的时候Generic.Show<int>(1);我们再指定具体的类型(此处是int),传进去就是int类型,所以就没有了装箱一说。以上种种均源自泛型的一个特点:延迟声明

    延迟声明:把参数类型的声明推迟到调用。(延迟声明是一种很nice的思想,推迟一切可以推迟的东西),延迟声明原理是什么呢?

    我们打印看一下List<int>的类型

    List1 和 Dictionary2 ,1和2代表占位符。现在简单说一下我们写的代码到0和1的过程:

    我们用VS编写的C#代码叫人类语言(人能看懂的语言),但是计算机只识别0和1。所以中间会有一系列转换操作。我们写的人类语言通过VS编译器编译成exe或者DLL,exe中包含一种中间语言叫IL,当我们运行exe时,.Net Framework为我们提供一种JIT(即时编译器)把IL转换成0和1供计算机识别。

    编译时生成List1 和 Dictionary2就是中间语言IL,当我们运行时,JIT会把调用时传入的具体类型(int,string)放入所占的位中,这期间是没有性能损耗的。(可以自己写一个小测试,用上面的泛型方法、普通方法和Object方法循环调用测试,然后查看耗时)。

泛型类

  • List<T> 、Dictionary<TKey, TValue>都是泛型类,下面我们自己创建一个泛型类

    再看一下有关泛型类的继承
  • 泛型类封装了不针对任何特定数据类型的操作。泛型类常用于容器类,如链表、哈希表、栈、队列、树等等。这些类中的操作,如对容器添加、删除元素,不论所存储的数据是何种类型,都执行几乎同样的操作。
  • 一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。
  • 更多详细请查看 –泛型类

泛型接口


  • 更多详细请查看-泛型接口

泛型委托

  • 委托 可以定义自己的类型参数,引用泛型委托的代码可以指定类型参数,就像实例化泛型类或调用泛型方法一样
  • 在泛型类内部定义的委托
  • 根据典型设计模式定义事件时,泛型委托尤其有用,因为发送方参数可以为强类型,不再需要强制转换成 object,或反向强制转换。

    以上代码算是一个观察者设计模式,后面单讲委托时我们深入了解一下。

泛型约束

  • 再把上面的Show<T>方法拿过来

    方法体内部只是简单打印了一下taparameter的值,实际工作中我们更多的是想要获取taparameter的属性或者方法(假设T是一个类)。在这种状态下是无法获取到的,怎么办呢?此时泛型约束闪亮登场。

    基类约束

    看到这估计要疯了,你这不是多此一举,我直接把Phone对象传递进去不就可以了吗?take it easy!

    现在我们再声明一个接口用来支付,当然并不是所有手机都有支付功能。

    然后我们修改一下Show<T>方法,增加一个IPay接口约束,形成了多重约束

    So,可以看出Show方法局限性很大,Show<T>方法就更方便,更有灵活性。

    无参构造、引用类型、值类型约束

Default关键字

  • 在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T
  • T 是引用类型还是值类型。
  • 如果 T 为值类型,则它是数值还是结构。
  • 给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。 解决方案是使用 default 关键字,此关键字对于引用类型会返回 null,对于数值类型会返回零,对于结构,此关键字将返回初始化为零或 null 的每个结构成员,具体取决于这些结构是值类型还是引用类型。 对于可以为 null 的值类型,默认返回 System.Nullable<T>,它像任何结构一样初始化。

未完待续……

 

如果转载,请给出原文链接,谢谢!


微信支付宝

如果本文帮助到您,请随意打赏作者