【C#进阶系列】11 泛型

泛型是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 = "213";
            Object s2 = "123";
            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了)
时间: 2024-10-05 06:16:05

【C#进阶系列】11 泛型的相关文章

【 D3.js 入门系列 — 11 】 入门总结

D3 新专题首页 一转眼,这个入门系列已经积累了二十二篇文章之多,我想作为 D3.js 这款数据可视化工具的入门来说已经足够了.相信仅仅要看完本系列.以后全然能够在辅以查询的情况下完毕大部分可视化工作. D3.js 最早的 v1.0 版本号是由 Michael Bostock 于2011年2月18日公布,其后经过多人的不断完好,眼下最新的版本号为 v3.4.11.从公布至今三年多的时间里,D3.js 在国外不断有人尝试并制作教程.成为了流行的数据可视化工具.可是眼下在国内能查询到中文资料还比較少

C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(二)

前言:上篇介绍了下封装BootstrapHelper的一些基础知识,这篇继续来完善下.参考HtmlHelper的方式,这篇博主先来封装下一些常用的表单组件.关于BootstrapHelper封装的意义何在,上篇评论里面已经讨论得太多,这里也不想过多纠结.总之一句话:凡事有得必有失,就看你怎么去取舍.有兴趣的可以看看,没兴趣的权当博主讲了个“笑话”吧. 本文原创地址:http://www.cnblogs.com/landeanfen/p/5746166.html BootstrapHelper系列

Wireshark入门与进阶系列(一)

摘自http://blog.csdn.net/howeverpf/article/details/40687049 Wireshark入门与进阶系列(一) “君子生非异也,善假于物也”---荀子 本文由CSDN-蚍蜉撼青松 [主页:http://blog.csdn.net/howeverpf]原创,转载请注明出处! 你在百度上输入关键字“Wireshark.使用.教程”,可以找到一大堆相关的资料.那么问题来了, 为什么我还要写这个系列的文章? 前面你能搜到的那些资料,大部分可能存在两个小问题:

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

前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原因无非以下两点:一是Repository的真实意图没有理解清楚,导致设计的紊乱,随着项目的横向和纵向扩展,到最后越来越难维护:二是赶时髦的为了“模式”而“模式”,仓储并非适用于所有项目,这就像没有任何一种架构能解决所有的设计难题一样.本篇通过这个设计的Demo来谈谈博主对仓储的理解,有不对的地方还望

Spring Boot进阶系列三

Thymeleaf是官方推荐的显示引擎,这篇文章主要介绍怎么让spring boot整合Thymeleaf.  它是一个适用于Web和独立环境的现代服务器端Java模板引擎. Thymeleaf的主要目标是给开发工作流程带来优雅的自然模板 - 可以在浏览器中正确显示的HTML,也可以用作静态原型,从而在开发团队中实现更强大的协作.通过Spring Framework模块,与喜欢的工具的集成,Thymeleaf是HTML5 JVM Web开发的理想选择. 1.自然模板官方示例 用Thymeleaf

Skype For Business 2015实战系列11:创建并发布拓扑

Skype For Business 2015实战系列11:创建并发布拓扑 Skype For Business Server安装前需要先定义好拓扑,因为我们要在拓扑中的每台服务器上安装 Skype for Business Server 系统,必须首先创建和发布一个拓扑.发布拓扑时,拓扑信息会载入中央管理存储数据库.如果这是 Enterprise Edition 池,您将在初次发布新拓扑时创建中央管理存储数据库.如果是 Standard Edition,则需要运行部署向导中的"准备第一个 St

Spring基础系列11 -- 自动创建Proxy

Spring基础系列11 -- 自动创建Proxy 转载:http://www.cnblogs.com/leiOOlei/p/3557964.html 在<Spring3系列9- Spring AOP——Advice>和<Spring3系列10- Spring AOP——Pointcut,Advisor拦截指定方法>中的例子中,在配置文件中,你必须手动为每一个需要AOP的bean创建Proxy bean(ProxyFactoryBean). 这不是一个好的体验,例如,你想让DAO层

JavaScript进阶系列06,事件委托

在"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数"中已经有了一个跨浏览器的事件处理机制.现在需要使用这个事件处理机制为页面元素注册事件方法. □ 点击页面任何部分触发事件 创建一个script1.js文件. (function() { eventUtility.addEvent(document, "click", function(evt) { alert('hello'); }); }(

【 D3.js 进阶系列 】 进阶总结

进阶系列的文章从去年10月开始写的,晃眼又是4个多月了,想在年前总结一下. 首先恭祝大家新年快乐.今年是羊年吧.前段时间和朋友聊天,聊到十二生肖里为什么没猫,我张口就道:不是因为十二生肖开会的时候猫迟到了吗? 呵呵,不知道这是谁给我灌输的观点.o(>﹏<)o 进阶系列的文章分为两部分,文章前括号里写有: [D3.js 进阶系列] [D3.js 选择集与数据详解] 虽然称之为"进阶",但并不是说一定要看完"入门"才能看.由于本人能力有限,不能很好地整理成由

JavaScript进阶系列02,函数作为参数以及在数组中的应用

有时候,把函数作为参数可以让代码更简洁. var calculator = { calculate: function(x, y, fn) { return fn(x, y); } }; var sum = function(x, y) { return x + y; }, diff = function (x, y) { return x - y; }; var sumResult = calculator.calculate(2, 1, sum), diffResult = calculat