CLR via C#深解笔记六 - 泛型

面向对象编程一个好处就是“代码重用”,极大提高了开发效率。如是,可以派生出一个类,让它继承基类的所有能力,派生类只需要重写虚方法,或添加一些新的方法,就可以定制派生类的行为,使之满足开发人员的需求。

泛型(generic)是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”。

简单地说,开发人员先定义好一个算法,比如排序、搜索、交换、比较或转换等。但是,定义算法的开发人员并不设定该算法要操作什么数据类型;该算法可以广泛地应用于不同类型的对象。然后,另一个开发人员只要指定了算法要操作的具体数据类型,就可以使用这个现成的算法了。例如,可用一个排序算法来操作Int32和String等类型的对象,或用一个比较算法来操作DateTime和Version等类型的对象。大多数算法都封装在一个类型中,CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。还允许创建泛型接口和泛型委托。

泛型为开发人员提供了一下优势:

#1,源代码保护。

#2,类型安全。编译器和CLR能理解开发人员的意图,并保证只有与指定数据类型兼容的对象才能随同算法使用。

#3,更加清晰的代码。减少了源代码中必须进行的转型次数,代码更容易编写和维护。

#4,更佳的性能。创建泛型算法来操作一种具体的值类型,所以值类型的实例能够以传值方式传递,CLR不再需要执行任何装箱操作。ArrayList来操作值类型(如Int32),会造成大量装箱操作,大量的垃圾回收。

泛型最明显的应用就是集合类。FCL已经定义了几个泛型集合类,其中,大多数类都在System.Collections.Generic 和System.Collections.ObjectModel命名空间中。

泛型基础结构

开发类型和封闭类型

我们关注CLR如何为应用程序使用的每个类型创建一个怎样的内部数据结构,这种数据结构称为类型对象(type object)。具有泛型类型参数的类型仍然是类型,CLR同样会为它创建一个内部类型对象。无论引用类型(类)、值类型(结构)、接口类型,还是委托类型,这一点都成立。具有泛型类型参数的类型称为开放类型(open type), CLR禁止构造开发类型的任何实例,类似CLR禁止构造接口类型的实例, 如List<>。

代码引用一个泛型类型时,可指定一组泛型类型实参。假如为所有类型实参传递的都是实际数据类型,类型就称为封闭类型(closed type),CLR允许构造封闭类型的实例,如List<String> 。需要注意的是,CLR会在类型对象内部分配类型的静态字段,因此每个封闭类型都有自己的静态字段。例如,List<T>定义了任何静态字段,都不会在一个List<DateTime>和一个List<String>之间共享:每个封闭类型对象都有它自己的静态字段。假如一个泛型类型定义了一个静态构造器,那么针对每个封闭类型,这个构造器都会执行一次。在泛型类型上定义一个静态构造器的目的是保证传递的类型实参满足特定的条件(约束)。如,希望一个泛型类型只用于处理枚举类型,就可以如下定义:

internal sealed class GenericTypeThatRequiresAnEnum<T> {

static GenericTypeThatRequiresAnEnum() {

if(!typeof(T).IsEnum) {

throw new ArgumentException("T must be an enmuerated type");

}

}

}

CLR提供了一个名为约束的功能,可利用它更好地定义一个泛型类型来支出哪些类型实参是有效的。

代码爆炸

使用泛型类型参数的一个方法在进行JIT编译时,CLR获取方法的IL,用指定的类型实参进行替换,然后创建恰当的本地代码(这些代码是为操作指定数据类型的方法“量身定制”的)。这样做有一个缺点:CLR要为每一种不同的方法/类型组合生成本地代码。这个现象称为代码爆炸(code explosion)。它可能造成应用程序的工作集显著增大,从而损害性能。

CLR其实内建了一些优化措施,能够缓解代码爆炸。假如为一个特定的类型实参调用一个方法,以后再次使用相同的类型实参来调用这个方法。CLR只会为这个方法/类型组合编译一次代码。如果一个程序集使用List<DateTime>, 一个完全不同的程序集(加载到一个AppDomain中)也使用List<DateTime>, CLR只会为List<DateTime>编译一次方法。这样显著缓解了代码爆炸。

CLR还提供了另一个优化措施,它认为所有引用类型实参都是完全相同的,所以代码可以共享。如,CLR为List<String>的方法编译的代码可直接用于List<Stream>的方法,因为String和Stream均为引用类型。事实上,对于任何引用类型,都会使用相同的代码。CLR之所以能执行这个优化,是因为所有引用类型的实参或变量实际只是指向堆上的对象的指针,而对象指针全部是以相同的方式来操作的。

但是,某个类型实参是值类型,CLR就必须专门为这个值类型生成本地代码,因为值类型的大小不定,还可能要用不同的本地CPU指令来操纵这些值。

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

时间: 2024-08-04 12:19:09

CLR via C#深解笔记六 - 泛型的相关文章

CLR via C#深解笔记三 - 基元类型、引用类型和值类型 | 类型和成员基础 | 常量和字段

编程语言的基元类型 某些数据类型如此常用,以至于许多编译器允许代码以简化的语法来操纵它们. System.Int32 a = new System.Int32();  // a = 0 a = 1; 等价于: int a = 1; 这种语法不仅增强了代码的可读性,其生成的IL代码与使用System.Int32时生成的IL代码是完全一致的. 编译器直接支持的数据类型称为基元类型(primitive type).基元类型直接映射到Framework类库(FCL)中存在的类型.如C#中,int直接映射

CLR via C#深解笔记四 - 方法、参数、属性

实例构造器和类(引用类型) 构造器(constructor)是允许将类型的实例初始化为良好状态的一种特殊方法.构造器方法在“方法定义元数据表”中始终叫.ctor. 创建一个引用类型的实例时: #1, 首先为实例的数据字段分配内存 #2, 然后初始化对象的附加字段(类型对象指针和同步块索引) #3, 最后调用类型的实例构造器来设置对象的初始状态 构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零.构造器没有显示重写的所有字段保证都有一个0或null值.和其它方法不同,实

CLR via C#深解笔记二 - 类型设计

类型基础 所有类型都从System.Object派生 CLR要求所有对象都用new 操作符来创建. Employee e = new Employee("Constructor Parameters"); 以下是 new 操作符所做的事情: #1, 计算类型及所有基类型(一直到System.Object, 虽然它没有定义自己的实例字段)中定义的所有实例字段需要的字节数. 堆上的每个对象还需要一些额外(overhead 开销成员)的成员 -- 即“类型对象指针”(type object

CLR via C#深解笔记五 - 事件

事件处理实际上是一种具有特殊签名的delegate, 像这个样子:public delegate void EventHandler(object sender, EventArgs e); 类型定义事件成员,就可以通知其他对象发生了特定的事情.如果定义一个事件成员,意味着类型要提供一下能力: #1,方法可登记也可注销它对该事件的关注 #2,该事件发生时,登记了的方法会收到通知. 类型之所以能够提供事件通知功能,是因为类型维护了一个已经登记方法的列表.事件发生后,类型将通知列表中所有已登记的方法

《CLR via C#》读书笔记 之 泛型

第十二章 泛型 2014-06-15 初始泛型12.3 泛型基础结构  12.3.1 开放类型与封闭类型  12.3.2 泛型类型和继承  12.3.3 泛型类型同一性  12.3.4 代码爆炸参考 初始泛型[1][2] 返回 泛型(generic)是CLR和编程语言提供一种特殊机制,它支持另一种形式的代码重用,即"算法重用". 简单地说,开发人员先定义好一个算法,比如排序.搜索.交换等.但是定义算法的开发人员并不设定该算法要操作什么数据类型:该算法可广泛地应用于不同类型的对象.然后,

Java泛型学习笔记 - (六)泛型的继承

在学习继承的时候, 我们已经知道可以将一个子类的对象赋值给其父类的对象, 也就是父类引用指向子类对象, 如: 1 Object obj = new Integer(10); 这其实就是面向对象编程中的is-a关系. 既然上面的代码正确, 那么在泛型中, 也可以使用如下代码: 1 public class Box<T> { 2 private T obj; 3 4 public Box() {} 5 6 public T getObj() { 7 return obj; 8 } 9 10 pub

java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessController的checkPerssiom方法,访问控制器AccessController的栈检查机制又遍历整个 PerssiomCollection来判断具体拥有什么权限一旦发现栈中一个权限不允许的时候抛出异常否则简单的返回,这个过程实际上比我的描述要复杂 得多,这里我只是简单的一句带过,因为这

初探swift语言的学习笔记六(ARC-自动引用计数,内存管理)

Swift使用自动引用计数(ARC)来管理应用程序的内存使用.这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理.当实例并不再被需要时,ARC会自动释放这些实例所使用的内存. 另外需要注意的: 引用计数仅仅作用于类实例上.结构和枚举是值类型,而非引用类型,所以不能被引用存储和传递. swift的ARC工作过程 每当创建一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息. 另外当实例不再被使用时,ARC会释放实例所占用的内存,这些

深搜笔记

看搜索已经很久了,对于搜索的思想从原来的死记硬背到现在终于懂了一点其实也蛮不错吧,我自己先总结出来了几条关于在图里面深搜的几条方法,仅供参考: 首先:我们得知道深搜是什么,其次于广搜的区别是什么.然后又哪些模板 举一个深搜例子:red and black:这是初学者最常见到的题.对于这题我们所要考虑的就是一个'.'的个数,这个题先要找到@的位置,这个好办,直接: for(int i=0;i<m;i++) { for(int j=0;j<n;j++) { if(map[i][j]=='@') }