泛型优势:

  • 源代码保护 使用泛型算法的开发人员不需要访问算法的源代码.(使用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. Is it bad to rely on foreign key cascading? 外键 级联操作

    Is it bad to rely on foreign key cascading? I'll preface前言 this by saying that I rarely delete rows ...

  2. DFS回溯-函数递归-xiaoz triangles

    题目:小z 的三角形 ★实验任务 三角形的第1 行有n 个由"+"和"-"组成的符号,以后每行符 号比上行少1 个,2 个同号下面是"+", ...

  3. win7(64)未在本地计算机上注册 Microsoft.Jet.OLEDB.4.0 提供程序

    注:本文为个人学习摘录,原文地址:http://blog.163.com/rihui_7/blog/static/2122851432013627103337825/ 1.以前在win7 64位系统上 ...

  4. Java中引用的详解

    Java中没有指针,到处都是引用(除了基本类型).所以,当然,你肯定知道java的引用,并用了很久,但是是不是对此了解地比较全面?而这些引用有什么作用,且有什么不同呢?Java中有个java.lang ...

  5. Linux——用户管理简单学习笔记(一)

    Linux用户分为三种: 1:超级用户(root,UID=0) 2:普通用户(UID 500-60000) 3:伪用户(UID 1-499)  伪用户: 1.伪用户与系统和程序服务相关 :nbin.d ...

  6. Intellij IDEA 使用spring-boot-devtools无效解决办法一

    Intellij IDEA 使用spring-boot-devtools maven依赖 ``` <dependency> <groupId>org.springframewo ...

  7. python网络编程之TCP通信实例

    一. server.py import socket host="localhost" port= s=socket.socket(socket.AF_INET,socket.SO ...

  8. 转载: 华为内部Web安全测试原则

    原链接:http://www.ha97.com/5520.html Web安全原则 1.认证模块必须采用防暴力破解机制,例如:验证码或者多次连续尝试登录失败后锁定帐号或IP. 说明:如采用多次连续尝试 ...

  9. CCF 100012. 技能树

    100012. 技能树 思路:区间dp. 状态:dp[i][j]表示节点为i,高度小于等于j的方案数. 状态转移:dp[i][j]=∑dp[k][j-1]*dp[i-1-k][j-1]. 节点为i,高 ...

  10. 【python】模块整理

    ---文件.系统--- import glob  # 给定路径下查找符合规则文件.三个匹配符:”*”, “?”, “[]”.”*”匹配0个或多个字符:”?”匹配单个字符:”[]”匹配指定范围内的字符, ...