第十二章 泛型
-
泛型:支持泛型值类型、泛型引用类型、泛型接口、泛型委托;允许在引用类型、值类型和接口中定义泛型方法;泛型参数变量要么称为T,要么以T开头
-
具有泛型类型参数的类型称为开放类型,不允许构造实例;当为所有参数类型传递了实际参数,类型就称为封闭类型,允许构造实例
-
泛型类型是类型,所以可以从其他类型派生;使用泛型类型并指定形参时,实际是定义一个新的类型(从泛型类型派生自的类型派生)。例:List<T>派生自Object,所以List<string>也派生自Object
-
泛型的同一性(不想让符号<>降低可读性时的处理)
- 使用class dtl : List<DateTime>,会失去同一性(typeof(dtl) != typeof( List<DateTime>))
- 可以使用 using dtl = System.Collections.Generic.List<DateTime>
- 可以直接var dtl = new List<DateTime>
-
代码爆炸(CLR每新出现一种类型的实参都要编译一次代码)
- 不同程序集中同一类型只编译一次(限定同一个domain)
- 所有引用类型都视为同一种实参(所有引用类型只编译一次)
-
可以定义泛型接口,如IEnumerator<T>
-
可以定义泛型委托,如Action<T>
-
泛型类型参数的逆变和协变
-
逆变性:用in关键字标记的泛型参数,可以更改为它的派生类(输入)
-
协变性:用out关键字标记的泛型参数,可以更改为它的基类(输出)
-
例:定义public delegate TResult Func<in T, out TResult>(T arg);
Func<Object, ArgumentException> fn1 = null,
Func<String, Exception> fn2 = fn1 是合法的(因为String从Object派生,Exception是ArgumentException的基类)
使用:Exception e = fn2(" ") ; (实参String->Object, 返回值ArgumentException->Exception)
-
委托和接口都可以将类型参数标记为逆变量和协变量
-
-
泛型方法,定义泛型类、结构或接口时,类中定义的方法可以使用类指定的类型参数
例:定义了class SomeClass<T>, 类中的方法可以使用类指定的T,如定义public T SomeMethod<T>()
-
由于out/ref实参传递的变量必须具有与方法参数相同的类型,所以可以使用泛型实现。
例:private static void Swap<T>(ref T o1, ref T o2);
-
C#编译器在调用泛型方法时可以进行类型推断,在可以确定类型实参的情况下可以不用<>指定
-
属性、索引器、事件、操作符方法、构造器、终结器本身不能有类型参数,但是可以在泛型类中定义,从而使用泛型类的类型参数
-
泛型可验证性:编译代码时需要确定代码适用于当前或已有或将来可能定义的任何类型,比如调用T.ToString(),对所有类型都有ToString()方法,所以代码合法
-
使用where关键字指定约束
- 约束可以应用于泛型类和泛型方法,不能用于重载(不能基于类型参数名称或约束重载,也不允许为重写方法的类型指定约束)
- 主要约束:主要约束指非密封类的一个引用类型(不包括Object, Array, Delegate, MulticastDelegate, ValueType, Enum, Void),能有零个或一个
- 特殊主要约束:class,指引用类型;struct,指值类型
- 次要约束:指接口类型,能有零个或多个
- 构造器约束:指拥有公共无参构造器的非抽象类型
-
其他可验证性问题
- 不能将泛型变量转型为其他变量(除非转成与约束兼容的类型)
- 不能将泛型变量设为null(除非泛型类型被约束成引用类型)
- 可以将泛型类型变量与null比较(==或!=),(虽然值类型不能与null比较(因为永远不会null),但是约束成值类型后,代码能通过编译,但不执行if逻辑,只生成永远为true的分支的代码)
- 同一泛型类型的两个变量,如果不能肯定是引用类型,则不能比较(T a,b;判断 a==b)
- 不能将操作符(+、-、*、/)用于泛型变量(不能编译)