泛型是CLR和编程语言提供的一种特殊机制,它用于满足“算法重用”  。

可以想象一下一个只有操作的参数的数据类型不同的策略模式,完全可以用泛型来化为一个函数。

以下是它的优势:

  • 类型安全

    • 给泛型算法应用一个具体的数据类型时,如果不兼容这种类型,就会编译错误或者报异常。
  • 更清晰的代码
    • 减少了强制转换,让代码更简洁
  • 更佳的性能
    • 用泛型可以有效避免装箱拆箱的操作,且无需在进行强制转换时验证是否类型安全,这两点都有效提高了代码的性能。

这就是为什么List<T>淘汰了ArrayList的原因,特别是在进行值类型操作时,因为装箱拆箱过多而差距很大。

约定:泛型参数要么为T要么以大写T开头,例如List<T>。

FCL中的泛型

System.Collections.Generic和System.Collections.ObjectModel命名空间中提供了多个泛型集合类和接口。

System.Collections.Concurrent命名空间则提供线程安全的泛型集合类。

System.Array类则提供了大量的静态泛型方法。

泛型的基础结构

.net 2.0才有泛型。

  • 开放类型和封闭类型

    • 之前我们讲到CLR会为各种类型创建类型对象,同样一个新的泛型类TroyList<T>也会创建一个类型对象,我们将具有泛型参数的类型称为开放类型。

      • 不能构造开放类型的实例
    • 而指定了泛型类型实参的泛型类型称为封闭类型,例如:TroyList<int>。
      • 可以构造封闭类型的实例
      • 如果TroyList<T>定义了静态字段或者方法,那么TroyList<int>和TroyList<string>之间并不共享,因为这其实是两个不同的类型对象。
  • 泛型类型的继承
    • 使用泛型类型并指定类型实参后,实际上是一个新的封闭类型,新的类型对象从泛型类型派生自的那个类型派生。即List<T>派生自Object,那么List<int>就派生自Object。
  • 关于代码爆炸的优化
    • 看到这里你可能想到了,一个开放类型实际上会有多个封闭类型,比如一个List<T>会有List<int>,List<string>等N多封闭类型。实际上就是N多的类型对象,生成N多的重复代码,于是这被称作代码爆炸。
    • 关于优化:
      • 两个不同的程序集用到同一种封闭类型,只会由JIT编译器变异一次
      • CLR认为所有用引用类型做类型实参的封闭类型完全相同,所以代码可以共享。也就是说List<String>和List<Stream>的方法编译后的代码可以通用。因为操作的不同的引用类型的地址大小都是一样的。

委托和接口的逆变和协变泛型类型实参

泛型委托和接口的每个泛型类型参数都可标记为协变量和逆变量,利用此功能可实现相同类型但实参类型不同的委托和接口的相互转换。(很绕,不明白可以看下面)

  • 不变量

    • 意味着泛型类型参数不可更改
  • 逆变量
    • 意味着泛型类型参数可以从一个类更改为它的派生类。用in标记,逆变量泛型类型参数只能出现在输入位置。
  • 协变量
    • 意味着泛型类型参数可以从一个类更改为它的基类。用out标记,协变量泛型类型参数只能出现在输出位置。

举个例子

public class 基类 { }
public class 派生类 : 基类 { }
public class Test{
public delegate TResult MyFunc<in T1, out TResult, T2>(T1 a, T2 b);//第一个为逆变量,第二个为协变量,第三个为不变量 void show() {
MyFunc<基类, 基类, 基类> fn1 = null;
//以下注释为我自己的理解方式,只是为了方便理解而已
MyFunc<派生类, 基类, 基类> fn2 = fn1;//MyFunc<派生类, 派生类, 基类> fn2 = fn1;转换错误
MyFunc<基类, Object, 基类> fn3 = fn1;//MyFunc<Object, Object, 基类> fn3 = fn1;转换错误
MyFunc<派生类, Object, 基类> fn4 = fn1;
}
}

依然很绕,实际上不懂也没关系,转换不了编译器自然会提示。了解有这个东西就行了,也建议用int和out指定泛型委托的类型变量。更多的时候我们会用自带的泛型委托Action和Func,这两个泛型委托的参数都用到in和out。

关于泛型方法的类型推断

 void Go() {
String s1 = "";
Object s2 = "";
Show(s1, s2);//不指定Show<T>的T的玩法就叫类型推断,类型推断通过传入的变量s1和变量s2的变量类型来推断,而不是实际类型。因为这里两个变量类型不同,所以函数编译不通过。
}
void Show<T>(T a,T b) { }

约束

泛型的约束是一个很有意思的事情。

void Show<T>(T a,T b) where T :IList { }

比如上面这个函数,约束传入的类型T必须实现了IList接口。

通过约束可以限制传入的类型,然而正式因为提供了这层约束,保证了传入的类型都实现了IList接口,我们就可以使用IList的各种方法了。

约束分类:

  • 主要约束

    • 主要约束可以是代表非密封类的一个引用类型。(可以指定0到1个主要约束)
    • 两个特殊的主要约束为class和struct,分别约束传入的参数为引用类型和值类型。(特例的特例,struct不能约束Nullable<T>)
    • 约束不能指定以下特殊引用类型:Object,Array,Delegate,MulticastDelegate,ValueType,Enum或者Void。
  • 次要约束
    • 次要约束代表接口类型。(可以指定0到多个次要约束)
    • 特殊的次要约束,即指定的两个泛型类型参数中,一个继承另一个,例如:where T2:T1。
  • 构造器约束
    • 构造器约束约束类型实参,一定是实现了公共无参构造函数的非抽象类型。(可以指定0到1个构造器约束)
    • 所有值类型都隐式提供了公共无参构造器。所以同时使用struct和new()约束被认为是多余的,会报错。

可验证性

以下几种情况因为代码不可验证是否合法,所以将报错:

  • 泛型类型变量的转换

    • 原因:不可将泛型类型T的变量转换为其它类型,因为T可能为任何变量,所以可能转换失败
    • void Show<T>(T obj){
      string a=(string)obj; //出错
      }
    • 解决方案:
      void Show<T>(T obj)
      {
      string a = obj as string;//对于string而言,其实这里用ToString方法可能更恰当一点
      }

      值类型可以先强制转换为object,再转为具体的值类型。然而我认为这样的代码还是需要开箱装箱的,也许可以考虑修改下算法。

  • 将泛型类型变量设为默认值
    • 原因:因为T可以是值类型和引用类型,所以不可能设置一个值类型或者引用类型的默认值
    • 解决方案:可以考虑加约束或者用default(T),作为默认值。
  • 两个泛型类型变量相互比较
    • 原因:因为非基元类型的值类型除非重载了==操作符,否则会报错。
    • 解决方案:可以考虑约束为class或者用Equals。(注意哦,有可能因为Equals的被覆盖所以具体不确定是判断同一性还是相等性)
  • 泛型类型变量作为操作数使用
    • 原因:因为非基元类型的值类型除非重载了操作符,否则会报错。
    • 解决方案:反射,操作符重载或者dynamic。(会有性能影响,我一般用dynamic了)

【C#进阶系列】12 泛型的更多相关文章

  1. .Net进阶系列(12)-异步多线程(Thread和ThreadPool)(被替换)

    一. Thread多线程   1. 两种使用方式 通过F12查看Thread后,发现有两类构造函数,ParameterizedThreadStart和ThreadStart,其中 ThreadStar ...

  2. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

  3. C#进阶系列 ---- 《CLR via C#》

      [C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...

  4. C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper

    前言:之前学习过很多的Bootstrap组件,博主就在脑海里构思:是否可以封装一套自己Bootstrap组件库呢.再加上看到MVC的Razor语法里面直接通过后台方法输出前端控件的方式,于是打算仿照H ...

  5. C#进阶系列——DDD领域驱动设计初探(一):聚合

    前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下DDD这么一个听上去高大上的 ...

  6. C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上)

    前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...

  7. C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下)

    前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...

  8. C#进阶系列——DDD领域驱动设计初探(四):WCF搭建

    前言:前面三篇分享了下DDD里面的两个主要特性:聚合和仓储.领域层的搭建基本完成,当然还涉及到领域事件和领域服务的部分,后面再项目搭建的过程中慢慢引入,博主的思路是先将整个架构走通,然后一步一步来添加 ...

  9. windows程序员进阶系列:《软件调试》之堆 (一)

    windows程序员进阶系列:<软件调试>之堆 (一) 堆是软件在运行时动态申请内存空间的主要途径.从堆上申请来的空间需要程序员自己申请和释放,且申请和释放操作必须绝对匹配.忘记释放或者多 ...

  10. Wireshark入门与进阶系列(一)

    摘自http://blog.csdn.net/howeverpf/article/details/40687049 Wireshark入门与进阶系列(一) “君子生非异也,善假于物也”---荀子 本文 ...

随机推荐

  1. Spring-Context之七:使用p-namesapce和c-namespace简化bean的定义

    在Spring中定义bean的方式多种多样,即使使用xml的方式来配置也能派生出很多不同的方式. 比如如下的bean定义: 1 2 3 4 5 6 7 8 9 10 11 12 <beans x ...

  2. vi小结1

    我使用xshell,vi里面中文乱码: http://www.cnblogs.com/TianFang/archive/2013/01/21/2870181.html 发现他的问题(gcc编译出错时会 ...

  3. 我心中的核心组件(可插拔的AOP)~第十五回 我的日志组件Logger.Core(策略,模版方法,工厂,单例等模式的使用)

    回到目录 之前的讲过两篇关于日志组件的文章,分别是<第一回  日志记录组件之自主的Vlog>和<第三回  日志记录组件之log4net>,而今天主要说一下我自己开发的另一种日志 ...

  4. 爱上MVC~在Views的多级文件夹~续~分部页的支持

    回到目录 之前写的一篇文章,主要针对View视图,它可以放在N级目录下,不必须非要在views/controller/action这种关系了,而在程序运行过程中,发现分页视图对本功能并不支持,原因很简 ...

  5. Paip.语义分析----情绪情感词汇表总结

    Paip.语义分析----情绪情感词汇表总结 以下词语是按感情色彩共分为十四类: 作者Attilax  艾龙,  EMAIL:1466519819@qq.com  来源:attilax的专栏 地址:h ...

  6. SQLServer数据库备份

    使用sql语句备份数据: BACKUP DATABASE 数据库名称 TO DISK = '存储备份文件的路径\备份名称.bak' WITH INIT 使用例子: BACKUP DATABASE Sh ...

  7. Android 常见Crash Log汇总

    一.BinderProxy@4479b390 is not valid; is your activity running? 原因分析: 因为使用了AsyncTask 异步线程在线程完成以后的onPo ...

  8. Android Studio 使用技巧

    1.导入Android Studio 工程的一些技巧 因为Gradle版本的问题,我们在使用AS导入工程的时候,经常会碰到本地没有该项目的Gradle版本,这时候AS就会去下载这个版本的Gradle, ...

  9. Javascript快速入门(下篇)

    Javascript, cheer up. Ajax:其通过在Web页面与服务器之间建立一个额外的处理层,这个处理层就被称为Ajax引擎,它解释来自用户的请求,在后台以异步的方式处理服务器通信,其结构 ...

  10. Form的enctype="multipart/form-data"作用

    <form class="form-horizontal" role="form" method="post" action=&quo ...