泛型的约束

默认情况下,泛型的类型参数(parameter)可以使任何类型的。
如果只允许使用特性的类型参数(argument),就可以指定约束。
where T : base-class //表示T必须是某个父类的子类
where T : interface //表示T必须实现了某个接口
where T : class //表示T必须是引用类型
where T : struct //表示T必须是值类型,不包括可空值类型
where T : new() //表示类型里面必须有个无参构造函数
where U : T //表示U继承于T这个参数

泛型的约束 例子

  1. class SomeClass{}
  2. interface InterFace1{}
  3. class GenericClass<T,U> where T:SomeClass,InterFace1 where U:new()
  4. {..}

泛型的约束可以作用于类型或方法的定义。
未命名图片.png
IComparable 会报错,
类型“int”不能用作泛型类型或方法“DemoClass.Max(T, T)”中的类型参数“T”。没有从“int”到“DOTNETDemo.IComparable”的装箱转换
struct Nullable<T> where T:struct { .. } // 本身意思表示可空值类型
未命名图片.png

裸类型约束

未命名图片.png
要求U继承于T,或者是和T匹配

泛型类型的子类

泛型class可以有子类,在子类里,可以继续让父类类型参数保持开放

  1. class Stack<T>{..}
  2. class SpecialStack<T> : Stack<T>{..}

在子类里,也可以使用具体的类型来关闭(封闭)父类的类型参数

  1. class IntStack:Stack<int>{..}

子类型也可以引入新的类型参数

  1. class List<T>{..}
  2. class KeyedList<T,Tkey> : List<T>{..}

技术上来讲,所有子类的类型参数都是新鲜的。你可以认为子类先把父类的类型参数(argument)给关闭了,然后又打开了。为这个先关闭后打开的类型参数(argument)带来新的名称或含义。

  1. class List<T>{..}
  2. class KeyedList<TElement,TKey> : List<TElment>{..}

自引用的泛型声明

在封闭类型参数(argument)的时候,该类型可以把它自己作为具体的类型。
未命名图片.png
class Foo where T: IComparable{..}
class Bar : Bar {..}

静态数据

针对每一个封闭类型,静态数据是唯一的
未命名图片.png

类型参数和转换

C#的转换操作符支持下列转换:
数值转换
引用转换
装箱拆箱转换
自定义转换
决定采用的是哪种转换,发生在编译时,根据已知类型的操作数来决定
因为泛型只有在运行起来时才知道是什么类型
所以

  1. StringBuilder Foo<T>(T arg)
  2. {
  3. if(arg is StringBuilder)
  4. return (StringBuilder)arg; //编译不过去
  5. }

使用as操作符

  1. StringBuilder Foo<T>(T arg)
  2. {
  3. StringBuilder sb = arg as StringBuilder;
  4. if(sb != null) return sb;
  5. }

更常见的解决方法

  1. return (StringBuilder)(object)arg;

拆箱的时候也可能发生歧义

  1. int Foo<T>(T x) => (int)x; //Compile-time error
  2. int Foo<T>(T x) => (int)(object)x;