我以前Post过关于Generics的一些Link,这里我就不再重复Generics的基本原理。以下的内容假设你对Generics有基本的了解。
Generic类或方法是有一个或多个类型参数(type parameter)的类或方法:
public class List<T>{
…
}
这样用户可以创建List<string>,List<int>等等。与System.Collections.ArrayList相比,List<T>具有类型安全性(编译时类型检查),高性能(无Boxing和Unboxing)等优点。然而在实现List<T>,你会发现如果T不受限制(without any constraint)的话,实现起来会非常困难。举个例子,假设List<T>的内部表示如下:
public class List<T> {
private T[] _array; // 存储数据的数组
private int _size; // List的大小
}
现在我们要实习一个在List中查找一个元素的方法。听起来很简单:
public int IndexOf(T t) {
for(int i = 0; i < _size; i++) {
if( array[i].Equals(t)) {
return i;
}
}
return -1;
}
看起来应该没什么问题,不过如果你要是在CLR Profiler下运行这段代码,你会发现在List中查找一个int时会有Boxing。如果我们看一下IL代码,就会发现原因:
.method public hidebysig instance int32 IndexOf(!0 t) cil managed
{
…
IL_0013: box !0
IL_0018: constrained. !0
IL_001e: callvirt instance bool [mscorlib]System.Object::Equals(object)
…
}
问题在于编译器在生成代码时要选择一个Equals,然而T不受任何限制,所以编译器只能选择Object.Equals(Object)。这里要补充一点:并不是所有的IL box指令最终都会转换为实际的box代码。JIT会对一些常见的语句进行优化。比如:
void Foo<T>(T t) {
if( t == null) {
…
}
}
对应的IL代码中有Box指令,但在Foo的实际汇编代码中,整个if快都会消失。
IL_0000: ldarg.1
IL_0001: box !0
IL_0006: brtrue.s …
所以判断一段代码是否导致Boxing的最准确的方法是使用CLR Profiler或用Windbg进行汇编级调试。
一个解决方法是对T加以限制。
public class List<T> where T : IComparable<T> {
}
public interface IComparable<T>
{
bool Equals(T other);
}
这样编译器可以选择IComparable<T>.Equals(T),从而避免Boxing。
这样做的缺点是List的用途将大受限制。
理想的解决方案是使用动态限制:
public int IndexOf(T t) {
where T: IComparable<T> {
…
}
welse { // welse 是我捏造的一个关键字
…
}
return -1;
}
遗憾的是Generics目前还不支持动态限制(上面的代码只作说明使用)。
那么在不受限制的List上如何写一个可以避免Boxing的查找方法呢?答案是使用Comparer<T>:
public int IndexOf(T t) {
// 注意将 Comparer<T>.Default 的值存储在局部变量中。
// 否则可能会有性能问题。
IComparer<T> c = Comparer<T>.Default;
for(int i = 0; i < _size; i++) {
if( c.Equals(array[i],t)) {
return i;
}
}
return -1;
}
接下来的问题是为什么Comparer<T> 可以避免Boxing呢?Comparer<T>使用了一个Trick(Idea来自于Anders Hejlsberg)。如下是Comparer<T>的实现框架:
public abstract class Comparer<T> : IComparer<T> {
static Comparer<T> defaultComparer;
public static IComparer<T> Default {
get {
if (defaultComparer == null) {
if (typeof(IComparable<T>).IsAssignableFrom(typeof(T))) {
defaultComparer=(IComparer<T>)Activator.CreateInstance(
typeof(GenericComparer<>).BindGenericParameters(new Type[] {typeof(T)}));
}
else { defaultComparer = new ObjectComparer(); }
}
return defaultComparer;
}
}
public abstract int Compare(T x, T y);
public virtual bool Equals(T x, T y) ;
}
public class GenericComparer<T>: Comparer<T> where T: IComparable<T> {
}
public class ObjectComparer<T>: Comparer<T> {
}
上面的代码有两个值得注意的地方:
(1) 在get_Default中有一段Reflection代码,目的是:
defaultComparer = new GenericComparer<T>();
上面的代码无法通过编译,因为T在此上下文中不受限制,无法满足GenericComparer的限制。
(2) 在GenericComparer中T受限于IComparable<T>,所以编译器会可以选择无Boxing的Equals方法。
据谣传(:)),Microsoft即将在网上提供Generic Collections源代码的下载。大家以后可以阅读源代码来更深入的理解Generics编程。
Copyright notes: all rights reserved by author.