我以前Post过关于Generics的一些Link,这里我就不再重复Generics的基本原理。以下的内容假设你对Generics有基本的了解。

 

Generic类或方法是有一个或多个类型参数(type parameter)的类或方法:

public class List<T>{

}

这样用户可以创建List<string>List<int>等等。与System.Collections.ArrayList相比,List<T>具有类型安全性(编译时类型检查),高性能(无BoxingUnboxing)等优点。然而在实现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>.EqualsT),从而避免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>使用了一个TrickIdea来自于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)       GenericComparerT受限于IComparable<T>,所以编译器会可以选择无BoxingEquals方法。

 

据谣传(:)),Microsoft即将在网上提供Generic Collections源代码的下载。大家以后可以阅读源代码来更深入的理解Generics编程。

 

 

 

 

Copyright notes: all rights reserved by author.