本系列未经许可,禁止转载(包括网络媒体刊载)
.NET泛型的一大特点是在编译阶段对类型参数不做任何假设。也就是说,面对类型参数T和他的变量,你没有什么能做的——不能调用除Object成员之外的任何方法,不能进行大多数运算符的运算等等。它提供了一个叫约束的机制,能在编译期对类型实参的取值进行一些检查。许多人都将约束视为在类型参数上提供操作支持的唯一方法,并大量使用——你有没有约束过IComparable呢?但是,这种做法是不对的,因为约束仅仅能检测声明类型是否实现了某接口或继承自某类,但通过接口和基类实现的多态机制是一个运行时检查的机制,约束没有从任何方面帮助接口和基类工作。实际上,绝大多数情况下,你用一个约束了IComparable的类型参数T来编程序,和直接用IComparable作为你的操作类型没有什么不同。
约束的真正目的是减少类型实参的取值范围,也就是降低抽象性,是一个泛型具体化的手法。只有当你清楚,你的目的就是“约束”本身,而不是想给类型参数增加些操作的时候,才应该使用约束。那么只想给类型参数增加操作应该怎么办呢?我推荐的方法是:将实现该操作的功能封装到一个外部的辅助类中。比如我们常常想约束IComparable,因为我们想比较类型参数对象的大小。有很多人都用这种方法:
[VB]
Sub Sort(Of T As IComparable(Of T))(arr As T())
[C#]
void Sort<T>(T[] arr) where T : IComparable<T>
显而易见,Sort需要数组元素能够比较大小,那么为什么约束不好呢?注意我们约束的是T实现IComparable(Of T),如果T实现的是IComparable呢,就不能比较大小了吗?约束无法表达“实现了IComparable(Of T)或IComparable”这种情况。更进一步,仅有这样的对象才能比较大小吗?有许多类型并没有实现这两个接口中的任何一个,但是仍有比较大小的可能,我们一旦约束就等于将他们拒之门外。因此约束是不适合解决此类问题的。记住:我们唯一需要的就是比较,因此正确的方法是这样:
[VB]
Sub Sort(Of T)(arr As T(), comparer As IComparer(Of T))
[C#]
void Sort<T>(T[] arr, IComparer<T> comparer)
注意我们对T现在没有任何限制,但是要求提供一个IComparer(Of T)的实例。这个IComparer(Of T)的实现和T可以没有关系,因此不仅T可以不知晓它的存在,还可以提供不只一种的比较方法。但是这样做,无形要求用户必须提供一个额外的辅助对象,对于一些显而易见可比较大小的对象(如Int32),这种要求显得有些多余。那我们的解决方法就是提供一个默认的比较器:
[VB]
Sub Sort(Of T)(arr As T())
Sort(arr, Comparable(Of T).Default)
End Sub
[C#]
void Sort<T>(T[] arr)
{
Sort(arr, Comparable<T>.Default);
}
代码中的Comparer(Of T)是System.Collection.Generic下的一个类型,专门用于提供类型默认的比较器。注意到什么了吗?我们现在没有约束了,但是对用户来说,和有约束时的语法一样简单而清晰。比较那些实现了IComparable和IComparable(Of T)的类型时,Comparer(Of T)都提供了支持,而在比较没有实现这类接口的自定义类型时,可自行实现IComparer(Of T)提供比较机制。完美解决。
这种方法还能进行一些约束都实现不了的做法:支持运算符。我们知道在类型参数上实现哪怕最简单的加法都是不允许的,而且没有任何接口可以帮你做到这一点。这时如果能够使用外部辅助类的做法,就能够突破这一恼人的限制,比如VBF就用了下面一个机制来计算类型参数的加法。
[VB]
Function Add(Of T)(a As T, b As T, calc As ICalculator(Of T))As T
Return calc.Add(a, b)
End Function
Function Add(Of T)(a As T, b As T)As T
Return Calculator(Of T).Default.Add(a, b)
End Function
[C#]
T Add<T>(T a, T b, ICalculator<T> calc)
{
return calc.Add(a, b);
}
T Add<T>(T a, T b)
{
return Calculator<T>.Default.Add(a, b);
}
现在剩下的问题就是,诸如Comparer(Of T)和Calculator(Of T)这类默认的比较器和计算器是如何实现的?如何保证所实现操作的高效率?这就是我们下一次的任务——类型字典和Type Traits。