泛型这个概念并不算新鲜的事物【java,c++】等语言中也都各自有。然而,c#中的泛型使用起来还是更为方便简洁一些【尤其是可以向泛型施加的多种约束】
这里不打算把泛型的基本概念全部罗列,只记录一些个人觉得较为重要的部分。
需要注意的是:
CLR会在类型对象内部分配类型的静态字段,因此每个封闭类型都有自己的静态字段。
也就是说:List<T>定义了任何静态字段,这些字段不会在List<DateTime>和List<String>
之间共享 ,每个封闭类型对象都有自己的静态字段
【为所有类型实参传递的都是实际数据类型,这种类型就称为封闭类型 ,与之对应的,具有泛型类型参数的类型称为开放类型】。
但是,假如一个泛型类型定义了一个静态构造器【类型构造器】,那么针对每个封闭类型,这个构造器都会执行一次【在泛型类型上定义类型构造器的目的是保证传递的类型实参满足特定的条件】。
这比较实用。看个例子:
internal sealed class RequiredEnum<T>{
static RequiredEnum(){
if(!typeof(T).IsEnum){
throw new ArgumentException("T must be an enum type");
}
}
}
而这一点,是无法通过泛型约束做到的。【仅枚举类型,不包括派生类】
如果某个【泛型】类型实参是值类型,CLR就必须专门为这种值类型生成本地代码。以为值类型的大小不定。即使两个值类型大小一样【比如Int32和UInt32】,CLR仍然无法共享代码,因为可能要用不同的本地CPU指令来操纵这些值。
说到泛型,得提提 逆变和协变
在C#中,逆变量:意味着泛型类型参数从一个基类改为派生类【所以”逆变“】,逆变只出现在输入位置,用in关键字进行标识。
协变: 意味着泛型类型参数从一个派生类型改为基类。协变只能出现在输出参数,用out进行标识。
需要注意的是:我们之前例子里的,T称为 不变量【泛型类型不能更改】
in,out只能修饰泛型接口或者泛型委托,无法修饰结构或者类。【此处的out 不要和平时说的ref/out这里的用法搞混了】
看个例子:
public delegate TResult Func<in T,out TResult>(T arg);//这是c#的一个预置委托
Func<object,ArgumentException> fn1 = null;
Func<string,Exception> fn2 = fn1;//这里不需要显式转型
认真看看这个例子,能发现什么吗?
如果去掉in,out修饰,就会报错:
无法将类型“Program.F<object,System.ArgumentException>”
隐式转换为“Program.F<string,System.Exception>”,
没有in,out修饰,那么它们就是不变量。必须一致。
object->string发生逆变【原因是,需要(输入)object类型的地方,总是可以(输入)一个string,因为string是object的子类。】
ArgumentException->Exception发生协变【需要(输出)ArgumentException的地方总是可以(输出)Exception,ArgumentException是Exception子类】
另外:
对于泛型类型参数,如果将实参传给用out/ref关键字修饰的方法(参数),就不允许可变性【协变/逆变】
delegate void Test<in T>(ref T t) ;//无法通过编译
错误为:
error CS1961: 变体无效:
类型参数“T”必须为对于“Program.Test<T>.Invoke(ref T)”有效的
固定式。“T”为 逆变。
至于泛型约束就不多说: class,struct,new()必须是最后一个约束【告诉编译器有一个无参构造函数,但是不允许和struct一起出现—–值类型总是具有无参构造】
//类型约束语法例
internal sealed class Demo<T>where T: class,new(){
...
}
//函数类型约束语法例
public void func<T>(T a)where T : struct{
...
}
需要注意的是:对于范围的限制【T必须是某些类型的子类型】,这里,object,Array,Enum,ValueType,Void,Delegate,MulticastDelegate等是不可以出现的。值类型也不可以出现【别忘了,值类型隐式密封!】