# [C# 基础知识系列]专题七: 泛型深入理解(一)
**引言:**
在上一个专题中介绍了C#2.0 中引入泛型的原因以及有了泛型后所带来的好处,然而上一专题相当于是介绍了泛型的一些基本知识的,对于泛型的性能为什么会比非泛型的性能高却没有给出理由,所以在这个专题就中将会介绍原因和一些关于泛型的其他知识。
**一、泛型类型和类型参数**
泛型类型和其他int,string一样都是一种类型,泛型类型有两种表现形式的:泛型**类型(包括类、接口、委托和结构,但是没有泛型枚举的)**和泛型方法。那什么样的类、接口、委托和方法才称作泛型类型的呢 ?我的理解是类、接口、委托、结构或方法中有**类型参数**就是泛型类型,这样就有类型参数的概念的。 **类型参数** ——是一个真实类型的一个占位符(**我想到一个很形象的比喻的,比如大家在学校的时候,一到中午下课的时候食堂人特别多的,所以很多应该都有用书本占位置的习惯的, 书本就相当于一个占位符,真真坐在位置上的当然是自己的,讲到占位置,以前听过我同学说,他们班有个很牛逼的MM,中午下完课的时候用手机占位子的,等它打完饭回来的时候手机已经不见, 当时听完我就和我同学说,你们班这位女生真牛逼的,后面我们就**![](https://box.kancloud.cn/2016-01-18_569ca4488de4a.gif)),泛型声明中,类型参数必须放在一对尖括号里面(即<>这个符号),并且用逗号分隔多个类型参数,如List<T>类中T就是**类型参数,**在**使用泛型类型或方法的时候,我们要用真实类型来代替**,就像用书本占位子一个,书本只是暂时的在那个位置上,等打好饭了就要换成你坐在位置上了,同样在C#中泛型也是同样道理,类型参数只是暂时的在那个位置,真真使用中要用真实的类型去代替它的位置,此时我们把真实类型又取名为**类型实参,**如上一专题的代码中List<int>,类型实参就是int(代替T的位置)。
如果没有为类型参数提供类型实参,此时我们就声明了一个未绑定的泛型类型,如果指定了类型实参,此时的类型就叫做已构造类型(这里同样可以以书占位置去理解),然而已构造类型又可以是**开放类型或封闭类型**的,这里先给出这个两个概念的定义的:**开放类型**——具有类型参数的类型就是开放类型(所有的未绑定的泛型类型都属于开放类型的),**封闭类型**——为每个类型参数都传递了实际的数据类型。对于开放类型,我们创建**开放类型的实例**。
_注意:在C#代码中,我们唯一可以看到未绑定泛型类型的地方(除了作为声明之外)就是在typeof操作符里。_
下面通过以下代码来更好的说明这点:
```
using System;
using System.Collections.Generic;
namespace CloseTypeAndOpenType
{
// 声明开放泛型类型
public sealed class DictionaryStringKey<T> : Dictionary<string, T>
{
}
public class Program
{
static void Main(string[] args)
{
object o = null;
// Dictionary<,>是一个开放类型,它有2个类型参数
Type t = typeof(Dictionary<,>);
// 创建开放类型的实例(创建失败,出现异常)
o = CreateInstance(t);
Console.WriteLine();
// DictionaryStringKey<>也是一个开放类型,但它有1个类型参数
t = typeof(DictionaryStringKey<>);
// 创建该类型的实例(同样会失败,出现异常)
o = CreateInstance(t);
Console.WriteLine();
// DictionaryStringKey<int>是一个封闭类型
t = typeof(DictionaryStringKey<int>);
// 创建封闭类型的一个实例(成功)
o = CreateInstance(t);
Console.WriteLine("对象类型 = " + o.GetType());
Console.Read();
}
// 创建类型
private static object CreateInstance(Type t)
{
object o = null;
try
{
// 使用指定类型t的默认构造函数来创建该类型的实例
o = Activator.CreateInstance(t);
Console.WriteLine("已创建{0}的实例", t.ToString());
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
return o;
}
}
}
```
运行结果为(从结果中也可以看出开放类型不能创建该类型的一个实例,异常信息中指出类型中包含泛型参数):
![](https://box.kancloud.cn/2016-01-23_56a2eb2a0ae52.png)
**二、泛型类型中的静态字段和静态构造函数**
首先实例字段是属于一个实例的,静态字段是从属于它们声明的类型,即如果在某个Myclass类中声明了一个静态字段field,则不管创建Myclass的多少个实例,也不管从Myclass中派生出多少个实例,都只有一个Myclass.x字段。然而**每个封闭类型都有它自己的静态字段(使用类型实参时,实际上CLR会定义一个新的类型对象, 所以每个静态字段都是不一样对象里面的静态字段,所以才会每个都有各自的值)** 通过以下代码来更好说明下——每个封闭类型都有它自己的静态字段:
View Code
```
namespace GenericStaticFieldAndStaticFunction
{
// 泛型类,具有一个类型参数
public static class TypeWithStaticField<T>
{
public static string field;
public static void OutField()
{
Console.WriteLine(field+":"+typeof(T).Name);
}
}
// 非泛型类
public static class NoGenericTypeWithStaticField
{
public static string field;
public static void OutField()
{
Console.WriteLine(field);
}
}
class Program
{
static void Main(string[] args)
{
// 使用类型实参时,实际上CLR会定义一个新的类型对象
// 所以每个静态字段都是不一样对象里面的静态字段,所以才会每个都有各自的值
// 对泛型类型类的静态字段赋值
TypeWithStaticField<int>.field = "一";
TypeWithStaticField<string>.field = "二";
TypeWithStaticField<Guid>.field = "三";
// 此时filed 值只会有一个值,每个赋值都是改变了原来的值
NoGenericTypeWithStaticField.field = "非泛型类静态字段一";
NoGenericTypeWithStaticField.field = "非泛型类静态字段二";
NoGenericTypeWithStaticField.field = "非泛型类静态字段三";
NoGenericTypeWithStaticField.OutField();
// 证明每个封闭类型都有一个静态字段
TypeWithStaticField<int>.OutField();
TypeWithStaticField<string>.OutField();
TypeWithStaticField<Guid>.OutField();
Console.Read();
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb2a1e2e0.png)
同样**每个封闭类型都有一个静态构造函数的**,通过下面的代码可以让大家更加明白这点:
```
// 静态构造函数的例子
public static class Outer<Tx>
{
// 嵌套类
public class Inner<Ty>
{
// 静态构造函数
static Inner()
{
Console.WriteLine("Outer<{0}>.Inner<{1}>", typeof(Tx), typeof(Ty));
}
public static void Print()
{
}
}
}
class Program
{
static void Main(string[] args)
{
#region 静态函数的演示
// 静态构造函数会运行多次
// 因为每个封闭类型都有单独的一个静态构造函数
Outer<int>.Inner<string>.Print();
Outer<int>.Inner<int>.Print();
Outer<string>.Inner<int>.Print();
Outer<string>.Inner<string>.Print();
Outer<object>.Inner<string>.Print();
Outer<object>.Inner<object>.Print();
Outer<string>.Inner<int>.Print();
Console.Read();
#endregion
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb2a2d96c.png)
从上图的运行结果可能会发现,我们代码中7个需要输出的,但是结果中只有6个结果输出的,这是因为**任何封闭类型的静态构造函数只执行一次**,最后一行的 Outer<string>.Inner<int>.Print();这行不会产生第7行输出, 因为Outer<string>.Inner<int>.Print();的静态构造函数在之前已经执行过的(第三行已经执行过了)。
**三、编译器如何解析泛型**
在上一个专题中,我只是贴出了泛型与非泛型的比较结果来说明泛型具有高性能的好处,却没有给出具体导致泛型比非泛型效率高的原因,所以在这个部分来剖析下泛型效率的具体原因。
这里先贴出上一个专题中说明泛型高性能好处的代码,然后再查看IL代码来说明泛型的高性能(针对泛型和非泛型,C#编译器是如何解析为IL代码的):
View Code
```
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace GeneralDemo
{
public class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
// 非泛型数组
ArrayList arraylist = new ArrayList();
// 泛型数组
List<int> genericlist= new List<int>();
// 开始计时
stopwatch.Start();
for (int i = 1; i < 10000000; i++)
{
//genericlist.Add(i);
arraylist.Add(i);
}
// 结束计时
stopwatch.Stop();
// 输出所用的时间
TimeSpan ts = stopwatch.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds/10);
Console.WriteLine("运行的时间: " + elapsedTime);
Console.Read();
}
}
}
```
当使用非泛型的的ArrayList数组时,IL的代码如下(这里只是贴出了部分主要的中间代码,具体的大家可以下载示例源码用IL反汇编程序查看的):
```
IL_001f: ldloc.1
IL_0020: ldloc.3
IL_0021: **box [mscorlib]System.Int32
** IL_0026: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
IL_002b: pop
IL_002c: nop
IL_002d: ldloc.3
IL_002e: ldc.i4.1
IL_002f: add
```
在上面的IL代码中,我用红色的标记的代码主要是在执行装箱操作(装箱过程肯定是要消耗的事件的吧, 就像生活中寄包裹一样,包装起来肯定是要花费一定的时间的, 装箱操作同样会,然而对于泛型类型就可以避免装箱操作,下面会贴出使用泛型类型的IL代码的截图)——这个操作也是影响非泛型的性能不如泛型类型的根本原因。然而为什么使用**ArrayList类型**在调用Add方法来向数组添加元素之前要装箱的呢?原因其实主要出在Add方法上的, 大家可以用**Reflector**反射工具查看ArrayList的Add方法定义,下面是一张Add方法原型的截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb2a4071a.png)
从上面截图可以看出,Add(objec value)需要接收object类型的参数,然而我们代码中需要传递的是int实参,此时就需要会发生装箱操作(值类型int转化为object引用类型,这个过程就是装箱操作),这样也就解释了为什么调用Add方法会执行装箱操作的, 同时也就说明泛型的高性能的好处。
下面是使用**泛型List<T>的IL代码截图**(从图片中可以看出,使用泛型时,没有执行装箱的操作,这样就少了装箱的时间,这样当然就运行的快了,性能就好了。):
![](https://box.kancloud.cn/2016-01-23_56a2eb2a5726c.png)
**四、小结**
说到这里本专题的内容也就介绍结束了,本专题主要是进一步介绍了泛型的其他内容的,由于篇幅的关于我将泛型的其他内容放在下一专题中,如果都在放在这个专题中内容会显得非常多,这样也不利于大家的消化和大家的阅读,所以我在下一个专题中继续介绍泛型的其他的一些内容。
下面先附上泛型专题中用到的所有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 领域驱动设计实战系列总结