# [C# 基础知识系列]专题八: 深入理解泛型(二)
**引言:**
本专题主要是承接上一个专题要继续介绍泛型的其他内容,这里就不多说了,就直接进入本专题的内容的。
**一、类型推断**
在我们写泛型代码的时候经常有大量的"<"和">"符号,这样有时候代码一多,也难免会让开发者在阅读代码过程中会觉得有点晕的,此时我们觉得晕的时候肯定就会这样想:是不是能够省掉一些"<" 和">"符号的呢?你有这种需求了, 当然微软这位好人肯定也会帮你解决问题的,这样就有了我们这部分的内容——**类型推断**,意味着编译器会在调用一个泛型方法时自动判断要使用的类型,(这里要注意的是:类型推断只使用于泛型方法,不适用于泛型类型),下面是演示代码:
```
using System;
namespace 类型推断例子
{
class Program
{
static void Main(string[] args)
{
int n1 = 1;
int n2 = 2;
// 没有类型推断时需要写的代码
// GenericMethodTest<int>(ref n1, ref n2);
// 有了类型推断后需要写的代码
// 此时编译器可以根据传递的实参 1和2来判断应该使用Int类型实参来调用泛型方法
// 可以看出有了类型推断之后少了<>,这样代码多的时候可以增强可读性
GenericMethodTest(ref n1, ref n2);
Console.WriteLine("n1的值现在为:" + n1);
Console.WriteLine("n2的值现在为:" + n2);
Console.Read();
//string t1 = "123";
//object t2 = "456";
//// 此时编译出错,不能推断类型
//// 使用类型推断时,C#使用变量的数据类型,而不是使用变量引用对象的数据类型
//// 所以下面的代码会出错,因为C#编译器发现t1是string,而t2是一个object类型
//// 即使 t2引用的是一个string,此时由于t1和t2是不同数据类型,编译器所以无法推断出类型,所以报错。
//GenericMethodTest(ref t1, ref t2);
}
// 类型推断的Demo
private static void GenericMethodTest<T>(ref T t1,ref T t2)
{
T temp = t1;
t1 = t2;
t2 = temp;
}
}
}
```
代码中都有详细的注释,这里就不解释了。
**二、类型约束**
如果大家看了我的上一个专题的话,就应该会注意到我在实现泛型类的时候用到了where T : IComparable,在上一个专题并没有和大家介绍这个是泛型的什么用法,这个用法就是这个部分要讲的类型约束,其实where T : IComparable这句代码也很好理解的,猜猜也明白的(如果是我不知道的话,应该是猜类型参数T要满足IComparable这个接口条件,因为Where就代表符合什么条件的意思,然而真真意思也确实如此的)下面就让我们具体看看泛型中的类型参数有哪几种约束的。 首先,编译泛型代码时,C#编译器肯定会对代码进行分析,如果我们像下面定义一个泛型类型方法时,编译器就会报错:
```
// 比较两个数的大小,返回大的那个
private static T max<T>(T obj1, T obj2)
{
if (obj1.CompareTo(obj2) > 0)
{
return obj1;
}
return obj2;
}
```
如果像上面一样定义泛型方法时,C#编译器会提示错误信息:“T”不包含“CompareTo”的定义,并且找不到可接受类型为“T”的第一个参数的扩展方法“CompareTo”。 这是因为此时类型参数T可以为任意类型,然而许多类型都没有提供CompareTo方法,所以C#编译器不能编译上面的代码,这时候我们(_编译器也是这么想的_)肯定会想——如果C#编译器知道类型参数T有CompareTo方法的话,这样上面的代码就可以被C#编译器验证的时候通过,就不会出现编译错误的(C#编译器感觉很人性化的,都会按照人的思考方式去解决问题的,那是因为编译器也是人开发出来的,当然会人性化的,因为开发人员当时就是这么想的,所以就把逻辑写到编译器的实现中去了),这样就让我们想对类型参数作出一定约束,缩小类型参数所代表的类型数量——这就是我们类型约束的目的,从而也很自然的有了**类型参数约束**(**这里通过对遇到的分析然后去想办法的解决的方式来引出类型约束的概念,主要是让大家可以明白C#中的语言特性提出来都是有原因,并不是说微软想提出来就提出来的,主要还是因为用户会有这样的需求,这样的方式我觉得可以让大家更加的明白C#语言特性的发展历程,从而更加深入理解C#,从我前面的专题也看的出来我这样介绍问题的方式的,不过这样也是我个人的理解,希望这样引入问题的方式对大家会有帮助,让大家更好的理解C#语言特性,如果大家对于对于有任何意见和建议的话,都可以在留言中提出的,如果觉得好的话,也麻烦表示认可下**)。所以上面的代码可以指定一个类型约束,让C#编译器知道这个类型参数一定会有CompareTo方法的,这样编译器就不会报错了,我们可以将上面代码改为(代码中T:IComparable<T>为类型参数T指定的类型实参都必须实现泛型**IComparable接口**):
```
// 比较两个数的大小,返回大的那个
private static T max<T>(T obj1, T obj2) **where T:IComparable<T>**
{
if (obj1.CompareTo(obj2) > 0)
{
return obj1;
}
return obj2;
}
```
类型约束就是用**where** 关键字来限制能**指定类型实参**的类型数量,如上面的where T:IComparable<T>语句。C# 中有4种约束可以使用,然而这4种约束的语法都差不多。(约束要放在泛型方法或泛型类型声明的末尾,并且要使用Where关键字)
(1) 引用类型约束
表示形式为 **T:class**, 确保传递的类型实参必须是引用类型(注意约束的类型参数和类型本身没有关系,意思就是说定义一个泛型结构体时,泛型类型一样可以约束为引用类型,此时结构体类型本身是值类型,而类型参数约束为引用类型),可以为任何的类、接口、委托或数组等;但是注意不能指定下面特殊的引用类型:**System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum和System.Void.**
如下面定义的泛型类:
```
using System.IO;
public class samplereference<T> where T : Stream
{
public void Test(T stream)
{
stream.Close();
}
}
```
上面代码中类型参数T设置了引用类型约束,**Where T:stream**的意思就是告诉编译器,传入的类型实参必须是System.IO.Stream或者从Stream中派生的一个类型,如果一个类型参数没有指定约束,则默认T为**System.Object**类型(相当于一个默认约束一样,就想每个类如果没有指定构造函数就会有默认的无参数构造函数,如果指定了带参数的构造函数,编译器就不会生成一个默认的构造函数)。然而,如果我们在代码中显示指定System.Object约束时,此时会编译器会报错:**约束不能是特殊类“object**”(这里大家可以自己试试看的)
(2)值类型约束
表示形式为**T:struct**,确保传递的类型实参时值类型,其中包括枚举,但是可空类型排除,(可空类型将会在后面专题有所介绍),如下面的示例:
```
// 值类型约束
public class samplevaluetype<T> where T : struct
{
public static T Test()
{
return new T();
}
}
```
在上面代码中,**new T()**是可以通过编译的,因为T 是一个值类型,而所有值类型都有一个公共的无参构造函数,然而,如果T不约束,或约束为引用类型时,此时上面的代码就会报错,因为有的引用类型没有公共的无参构造函数的。
(3)构造函数类型约束
表示形式为**T:new(),**如果类型参数有多个约束时,此约束必须为最后指定。确保指定的类型实参有一个公共无参构造函数的非抽象类型,这适用于:所有值类型;所有非静态、非抽象、没有显示声明的构造函数的类(前面括号中已经说了,如果显示声明带参数的构造函数,则编译器就不会为类生成一个默认的无参构造函数,大家可以通过IL反汇编程序查看下的,这里就不贴图了);显示声明了一个公共无参构造函数的所有非抽象类。(注意: 如果同时指定构造器约束和struct约束,C#编译器会认为这是一个错误,因为这样的指定是多余的,所有值类型都隐式提供一个无参公共构造函数,就如定义接口指定访问类型为public一样,编译器也会报错,因为接口一定是public的,这样的做只多余的,所以会报错。)
(4)转换类型约束
表示形式为 **T:基类名** (确保指定的类型实参必须是基类或派生自基类的子类)**或T:接口名**(确保指定的类型实参必须是接口或实现了该接口的类) **或T:U**(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数)。转换约束的例子如下:
| 声明 | 已构造类型的例子 |
| --- | --- |
| Class Sample<T> where T: Stream | Sample<Stream>有效的Sample<string>无效的 |
| Class Sample<T> where T: IDisposable | Sample<Stream >有效的Sample<StringBuilder>无效的 |
| Class Sample<T,U> where T: U | Sample<Stream,IDispsable>有效的Sample<string,IDisposable>无效的 |
(5)组合约束(第五种约束就是前面的4种约束的组合)
将多个不同种类的约束合并在一起的情况就是组合约束了。(注意,没有任何类型即时引用类型又是值类型的,所以引用约束和值约束不能同时使用)如果存在多个转换类型约束时,如果其中一个是类,则类必须放在接口的前面。不同的类型参数可以有不同的约束,但是他们分别要由一个单独的where关键字。下面看一些有效和无效的例子来让大家加深印象:
有效:
class Sample<T> where T:class, IDisposable, new();
class Sample<T,U> where T:class where U: struct
**无效的:**
class Sample<T> where T: class, struct (没有任何类型即时引用类型又是值类型的,所以为无效的)
class Sample<T> where T: Stream, class (引用类型约束应该为第一个约束,放在最前面,所以为无效的)
class Sample<T> where T: new(), Stream (构造函数约束必须放在最后面,所以为无效)
class Sample<T> where T: IDisposable, Stream(类必须放在接口前面,所以为无效的)
class Sample<T,U> where T: struct where U:class, T (类型形参“T”具有“struct”约束,因此“T”不能用作“U”的约束,所以为无效的)
class Sample<T,U> where T:Stream, U:IDisposable(不同的类型参数可以有不同的约束,但是他们分别要由一个单独的where关键字,所以为无效的)
**三、利用反射调用泛型方法**
下面就直接通过一个例子来演示如何利用反射来动态调用泛型方法的(关于反射的内容可以我博客中的这篇文章: [http://www.cnblogs.com/zhili/archive/2012/07/08/AssemblyLoad_and_Reflection.html](http://www.cnblogs.com/zhili/archive/2012/07/08/AssemblyLoad_and_Reflection.html)),演示代码如下:
```
using System;
using System.Reflection;
namespace ReflectionGenericMethod
{
class Program
{
static void Main(string[] args)
{
Test test = new Test();
Type type = test.GetType();
// 首先,获得方法的定义
// 如果不传入BindFlags实参,GetMethod方法只返回公共成员
// 这里我指定了NonPublic,也就是返回私有成员
// (这里要注意的是,如果指定了Public或NonPublic的话,
// 必须要同时指定Instance|Static,否则不返回成员,具体大家可以用代码来测试的)
MethodInfo methodefine = type.GetMethod("PrintTypeParameterMethod", BindingFlags.NonPublic|BindingFlags.Instance|BindingFlags.Static);
MethodInfo constructed;
// 使用MakeGenericMethod方法来获得一个已构造的泛型方法
constructed = methodefine.MakeGenericMethod(typeof(string));
// 泛型方法的调用
constructed.Invoke(null,null);
Console.Read();
}
}
public class Test
{
private static void PrintTypeParameterMethod<T>()
{
Console.WriteLine(typeof(T));
}
}
}
```
上面代码在调用泛型方法时传入的两个实参都是null,传入第一个为null是因为调用的是一个静态方法, 第二null是因为调用的方法是个无参的方法。 运行结果截图(结果是输出出 类型实参的类型,结果和我们预期的一样):
![](https://box.kancloud.cn/2016-01-23_56a2eb2a6d11e.png)
**四、小结**
说到这里泛型的内容都已经介绍完了,本系列用了三个专题来介绍泛型,文章内容都基本采用提出疑问(为什么有泛型)到解释疑问,再到深入理解泛型的方式(个人认为这样的讲解方式不错的,如果大家有更好的讲解方式可以在下面留言给我),希望这种方式可以让大家知道泛型的起源,从而更好的理解泛型。后面一专题将和大家介绍了C#4.0中对泛型的改进——**泛型的可变性**。
泛型专题中用到的所有Demo的源代码:[http://files.cnblogs.com/zhili/GeneralDemo.zip](http://files.cnblogs.com/zhili/GeneralDemo.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结