泛型
JIT编译器如何处理泛型
使用泛型类型参数的一个方法在进行JIT编译时,CLR获取方法的IL,使用指定的类型实参进行替换,然后创建恰当的本地代码。JIT为每个值类型实参(int, long, float)都创建不同的本地代码,为引用类型(string, stream)共享相同的本地代码。之所以可以这样是因为所有引用都具有相同的大小,对其操作也是一样的。
优点
想知道泛型的优点可以对比ArrayList和List<T>集合,ArrayList集合接受的参数是object类型,对于值类型需要装箱操作,如果需要还得拆箱,这会造成性能丢失和拆箱可能抛出异常,对于引用类型都会转换成object类型。如果代码如下:
ArrayList list = new ArrayList();
list.Add(2);
list.Add("abc");
然后
int sum = 0;
foreach(int l in list)
{
sum += l;
}
这代码可以通过编译,但是在运行时出错,我们知道尽可能的在编译时期检查出错误,如果使用泛型List<T>集合
List<int> list = new List<int>();
list.Add("abc");
会编译通不过,因此泛型在编译时期增强了类型的安全性。
继续上述的例子,使用ArrayList的Add方法添加值类型,会发生装箱操作,而List<T>则不用,因此泛型提升了性能。
使用泛型减少了类型转换的次数,使代码的更清晰。
泛型类型和泛型方法
泛型类型包括泛型类,泛型委托和泛型接口
泛型方法:
类型推断:
假如有
public static void Display(string str);
public static void Display<T>(T t);
调用时
Display("alab"); // 调用第一个,因为编译器会选择更明确的匹配,调用Dsiplay(string str)
Display(2); // 类型推断,与Display<int>(2)等价
Display<int>(2);
Display<string>("alab"); // 明确指出,调用的是Display<T>(T t)
约束:
泛型类型和泛型方法都支持约束,比如
private static T Min<T>(T o1, T o2)
{
if (o1.CompareTo(o2) < 0) ) return o1;
return o2;
}
这个是编译不过的,因为许多类型没有提供CompareTo方法,如果这样,泛型是在弱爆了,还好有约束,可以限定类型实参的类型数量,我们只需private static T Min<T>(T o1, T o2) where T:IComparable<T>即可,限定类型参数必须实现IComparable接口。
约束分为3种:
1. 主要约束:class, struct;分别约束为引用类型和值类型,但是引用类型有一些特例,比如System.Object等是不行的。比如约束为class之后可以设定T t = null了。必须兼容引用类型,比如Stream,要么是Stream,好么就是继承于Stream
2. 次要约束:接口,类型参数;即实现接口,兼容类型参数约束
3. 构造器约束:new() 表示类型参数必须实现了一个公共无参数的构造器,会和struct起冲突,因为struct默认就有公共无参数的构造器。
泛型委托和接口的逆变和协变泛型类型参数
逆变量:泛型类型实参可以从一个基类更改为该类的派生类,使用in关键字修饰,用于函数参数
协变量:泛型类型实参可以从一个类更改为其基类,使用out关键字修饰,用于函数返回值
支持逆变性和协变性使定义的委托可以再更多的情形下使用。
我们分析一下其合理性,假如有一个委托类型定义
public delegate TResult Func<in T, out TResult>(T arg);
委托变量 Func<MemoryStream, object> fn1 = null;
那么函数 public object Fun1(MemoryStream o)肯定匹配委托变量fn1;
public string Fun2(Stream s)也是匹配的。
定义泛型委托实例的时候可以把形参定义的范围小一点,返回值大一点。