泛型优势:

  • 源代码保护 使用泛型算法的开发人员不需要访问算法的源代码.(使用c++模板的泛型技术,算法的源代码必须提供给使用算法的用户)
  • 类型安全List<DateTime>实例添加一个String对象会报错.
  • 更清晰的代码 减少了源代码中必须进行的强制类型转换次数,使代码更容易编写和维护.
  • 更佳的性能 对于值类型实例,可以减少装箱拆箱次数.

12.1 FCL中的泛型

12.2 泛型基础结构

12.2.1 开放类型和封闭类型

  • 具有泛型类型参数的类型称为开放类型,CLR禁止构造开放类型的任何实例.
  • 为所有类型参数都传递了实际的数据类型,类型就成为封闭类型.CLR允许构造封闭类型的实例.
  • 每个封闭类型/封闭类型对象都有自己的静态字段.这些字段不会在一个List<DateTime>和一个List<String>之间共享.
  • 假如泛型类型定义了静态构造器,那么针对每个封闭类型,这个构造器都会执行一次.泛型类型定义静态构造器的目的是保证传递的类型实参满足特定条件.比如可以通过这样定义只能处理枚举类型的泛型类型:
    internal sealed class GenericTypeThatRequiresAnEnum<T>
{
static GenericTypeThatRequiresAnEnum()
{
if (!typeof(T).IsEnum)
throw new ArgumentException("T must be an enumerated type");
}
}

12.2.2 泛型类型和继承

12.2.3 泛型类型同一性

绝对不要单纯出于增强源码可读性的目的来定义一个新类.如:internal sealed class DateTimeList:List<DateTime>{ }

12.2.4 代码爆炸

CLR为应对代码爆炸的一些优化措施:

  • 如果一个程序集使用List<DateTime>,一个完全不同的程序集(加载到同一个AppDomain中)也使用List<DateTime>,CLR只为List<DateTime>编译一次方法.
  • CLR认为所有引用类型实参都完全相同,代码能够共享.但如果类型实参是值类型,CLR就必须专门为那个值类型生成本机代码.因为引用类型的实参或变量实际只是指向堆上对象的指针,大小固定;而值类型的大小不定.

12.3 泛型接口

12.4 泛型委托

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

泛型类型参数可以是以下任何一种形式:

  • 不变量(invariant) 意味着泛型类型参数不能更改.
  • 逆变量(contravariant) 意味着泛型类型参数可以从一个类更改为它的某个派生类.c#是用 in 关键字标记逆变量形式的泛型类型参数.逆变量泛型类型参数只出现在输入位置,比如作为方法的参数.
  • 协变量(covariant) 意味着泛型类型参数可以从一个类更改为它的某个基类. c#是用 out 关键字标记协变量形式的泛型类型参数.协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型.
    public delegate TResult Func<int T, out TResult>(T arg);

    Func<Object, ArgumentException> fn1=null;

    Func<string, Exception> fn2 = fn1; //不需要显式转型
Exception e = fn2("");
  • 只有编译器能验证类型之间存在引用转换,这些可变性才有用.换言之,由于需要装箱,所以值类型不具有这种可变性.
  • 对于泛型类型参数,如果要将该类型的实参传给使用 out 或 ref 关键字的方法,便不允许可变性.
  • 使用要获取泛型参数和返回值的委托时,或者具有泛型参数的接口,建议尽量为逆变性和协变性指定inout关键字.

12.6 泛型方法

  • c#编译器支持在调用泛型方法时进行类型推断.
        private static void Swap<T>(ref T o1, ref T o2)
{
T temp = o1;
o1 = o2;
o2 = temp;
} private static void CallingSwapUsingInference() {
int n1 = 1, n2 = 2;
Swap(ref n1, ref n2); //调用Swap<int> string s1 = "Aidan";
object s2 = "Grant";
Swap(ref s1,ref s2); //错误,不能推断类型
}
  • 类型可定义多个方法,让其中一个方法接受具体数据类型,让另一个接受泛型类型参数.编译器会优先考虑较明确的匹配,再考虑泛型匹配.

12.7 泛型和其它成员

在C#中,属性\索引器\事件\操作符方法\构造器和终结器本身不能有类型参数.但它们能在泛型类型中定义,而且这些成员中的代码能使用类型的类型参数.

12.8 可验证性和约束

        //C#的where关键字告诉编译器,为T指定的任何类型都必须实现
//同类型(T)的泛型IComparable接口.
private static T Min<T>(T o1, T o2) where T : IComparable<T>
{
if (o1.CompareTo(o2) < 0) return o1;
return o2;
}
  • CLR 不允许基于类型参数名称或约束来进行重载;只能基于元数(类型参数个数)对类型或方法进行重载.
  • 重写带有泛型约束的虚方法时,不必(也不允许)为重写方法的类型参数指定任何约束.但类型参数的名称是可以改变的.

12.8.1 主要约束

  • 类型参数可以指定零个或者一个主要约束.主要约束可以是代表非密封类的一个引用类型.不能指定以下特殊引用类型:System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum或者System.Void
    //一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型.
internal sealed class PrimaryConstraintOfStream<T> where T : Stream
{
public void M(T stream) {
stream.Close(); //正确
}
}
  • 有两个特殊的主要约束:classstruct. 其中, class 约束向编译器承诺类型实参是引用类型.任何类类型,接口类型,委托类型或者数组类型都满足这个约束.
    internal sealed class PrimaryConstraintOfClass<T> where T : class
{
public void M() {
T temp = null; //允许,因为T肯定是引用类型
}
}
  • struct 约束向编译器承诺类型实参是值类型.但不包括System.Nullable<T>.
    internal sealed class PrimaryConstraintOfStruct<T> where T : struct
{
public static T Factory () {
//允许. 因为所有值类型都隐式有一个公共无参构造器.
return new T();
}
}

12.8.2 次要约束

  • 类型参数可以指定零个或者多个次要约束,次要约束代表接口类型.
    internal sealed class ConstraintOfClass<T> where T : class, IComparable, IComparable<T>
{
public int CompareTo(T o1, T o2)
{
return o1.CompareTo(o2); //正确
}
}

12.8.3 构造器约束

  • 类型参数可指定零个或一个构造器约束,它向编译器承诺类型实参是实现了公共无参构造器的非抽象类.
    internal sealed class ConstructorConstraint<T> where T : new()
{
public static T Factory()
{
//允许. 因为所有值类型都隐式有一个公共无参构造器.
//而如果指定的是引用类型,约束也要求它提供公共无参构造器
return new T();
}
}

12.8.4 其它可验证性问题

  • 可以使用 T temp = default(T) 的方式为T类型的变量设置默认值.如果T是引用类型,就将temp设为null;如果是值类型,就将temp的所有位设为0.
  • 无论泛型类型是否被约束,使用==或!=操作符将泛型类型变量与 null 进行比较都是合法的:
        private static void ComparingAGenericTypeVariableWithNull<T>(T obj)
{
if (obj == null) { /*对于值类型,永远都不会执行*/}
}

但如果T被约束成struct,c#编译器会报错.值类型的变量不能与null进行比较,因为结果始终一样.

  • 如果泛型类型参数不能肯定是引用类型,对同一个泛型类型的两个变量进行比较是非法的:
    private static void ComparingTwoGenericTypeVariables<T>(T o1, T o2)
{
if (o1 == o2) { } //错误
}

T被约束成class能编译通过; 但如果约束成struct ,编译器会报错.

  • 不能将应用于基元类型的操作符(比如+,-,*和/)应用于泛型类型的变量.

返回目录

<NET CLR via c# 第4版>笔记 第12章 泛型的更多相关文章

  1. <NET CLR via c# 第4版>笔记 第19章 可空值类型

    System.Nullable<T> 是结构. 19.1 C# 对可空值类型的支持 C# 允许用问号表示法来声明可空值类型,如: Int32? x = 5; Int32? y = null ...

  2. <NET CLR via c# 第4版>笔记 第18章 定制特性

    18.1 使用定制特性 FCL 中的几个常用定制特性. DllImport 特性应用于方法,告诉 CLR 该方法的实现位于指定 DLL 的非托管代码中. Serializable 特性应用于类型,告诉 ...

  3. <NET CLR via c# 第4版>笔记 第17章 委托

    17.1 初识委托 .net 通过委托来提供回调函数机制. 委托确保回调方法是类型安全的. 委托允许顺序调用多个方法. 17.2 用委托回调静态方法 将方法绑定到委托时,C# 和 CLR 都允许引用类 ...

  4. <NET CLR via c# 第4版>笔记 第16章 数组

    //创建一个一维数组 int[] myIntegers; //声明一个数组引用 myIntegers = new int[100]; //创建含有100个int的数组 //创建一个二维数组 doubl ...

  5. <NET CLR via c# 第4版>笔记 第13章 接口

    13.1 类和接口继承 13.2 定义接口 C#用 interface 关键字定义接口.接口中可定义方法,事件,无参属性和有参属性(C#的索引器),但不能定义任何构造器方法,也不能定义任何实例字段. ...

  6. <NET CLR via c# 第4版>笔记 第5章 基元类型、引用类型和值类型

    5.1 编程语言的基元类型 c#不管在什么操作系统上运行,int始终映射到System.Int32; long始终映射到System.Int64 可以通过checked/unchecked操作符/语句 ...

  7. <NET CLR via c# 第4版>笔记 第6章 类型和成员基础

    6.1 类型的各种成员 6.2 类型的可见性 public 全部可见 internal 程序集内可见(如忽略,默认为internal) 可通过设定友元程序集,允许其它程序集访问该程序集中的所有inte ...

  8. <NET CLR via c# 第4版>笔记 第7章 常量和字段

    7.1 常量 常量 是值从不变化的符号.定义常量符号时,它的值必须能够在编译时确定. 只能定义编译器识别的基元类型的常量,如果是非基元类型,需把值设为null. 常量的值直接嵌入代码,所以不能获取常量 ...

  9. <NET CLR via c# 第4版>笔记 第8章 方法

    8.1 实例构造器和类(引用类型) 构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零 .没有被构造器显式重写的所有字段都保证获得 0 或 null 值. 构造器不能被继承 ...

随机推荐

  1. Tomcat下载及配置(windows系统)

    1.去官网下载 下载安装版的直接安装即可,下载解压版(推荐)的往下看. 2.win键+R键,输入sysdm.cpl点击确定,弹出系统属性窗口,选择菜单栏的高级选项卡,然后点击环境变量. 3.在弹出的窗 ...

  2. IDEA Spring-boot-devTools 无效解决办法二

    转载地址:Intellij IDEA 使用Spring-boot-devTools无效解决办法 相信大部分使用Intellij的同学都会遇到这个问题,即使项目使用了spring-boot-devtoo ...

  3. 未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0

    错误描述: 错误原因: 因为引用出了问题,在你的程序集里面找不到的Newtonsoft.Json,所以它就拿从系统盘里面预装的旧版的来用,结果就报版本错误了. 解决方案: web.config  的  ...

  4. NotifyIcon实现托盘程序

    NotifyIcon 控件的常用属性属性:Icon类型:System.Drawing.Icon说明:将在系统任务栏中显示的图标.可以在设计时指定,也可在运行时动态指定.属性:Text类型:String ...

  5. 清空mailq 队列里面的邮件

    tmp_=`mailq | grep -E "root" | awk '{print $1}'` for i in $tmp_;do postsuper -d $i;done po ...

  6. 12月7日,几个错误,拼写错误,遗漏符号:,记忆有误,max-width的作用。gem mini_magick, simple_form

    ❌: 1. /Users/chentianwei/jdstore3/jdstore/config/routes.rb:6:in `block in <top (required)>': u ...

  7. codeforces 497c//Distributing Parts// Codeforces Round #283(Div. 1)

    题意:有n个区间[ai,bi],然后有n个人落在[ci,di],每个人能用ki次.问一种方式站满n个区间. 两种区间都用先x后y的升序排序.对于当前的区间[ai,bi],将ci值小于当前ai的全部放入 ...

  8. 解决导入Gradle项目遇到的问题

    Gradle安装好了,插件也在eclipse中配置好了,却不会导入,尴尬.这里我就给大家介绍几个在配置 导入项目所遇到的问题: 分别选择Browse选中本地目录,和BuildModel创建相关的项目文 ...

  9. 『cs231n』作业3问题1选讲_通过代码理解RNN&图像标注训练

    一份不错的作业3资料(含答案) RNN神经元理解 单个RNN神经元行为 括号中表示的是维度 向前传播 def rnn_step_forward(x, prev_h, Wx, Wh, b): " ...

  10. 2-16 MySQL字段约束-索引-外键

    一:字段修饰符 1:null和not null修饰符 我们通过这个例子来看看 mysql> create table worker(id int not null,name varchar(8) ...