可以看到,MyClass<int>和MyClass<double>是属于mscorlib配件集的类(动态创建的),而类MyClass<t>属于我自建的配件集。
13. 泛型的局限性
至此,我们已了解了泛型的强大威力。是否其也有不足呢?我发现了一处。我希望微软能够明确指出泛型存在的这一局制性。在表达约束的时候,我们能指定参数类型必须继承自一个类。然而,指定参数必须是某种类的基类型该如何呢?为什么要那样做呢?
在例4中,我展示了一个Copy()方法,它能够把一个源List的内容复制到一个目标list中去。我可以象如下方式使用它:
List<Apple> appleList1 = new List<Apple>();
List<Apple> appleList2 = new List<Apple>();
…
Copy(appleList1, appleList2);
然而,如果我想要把apple对象从一个列表复制到另一个Fruit列表(Apple继承自Fruit),情况会如何呢?当然,一个Fruit列表可以容纳Apple对象。所以我要这样编写代码:
List<Apple> appleList1 = new List<Apple>();
List<Fruit> fruitsList2 = new List<Fruit>();
…
Copy(appleList1, fruitsList2);
这不会成功编译。你将得到一个错误:
Error 1 The type arguments for method
’TestApp.Program.Copy<t>(System.Collections.Generic.List<t>,
System.Collections.Generic.List<t>)’ cannot be inferred from the usage.
编译器基于调用参数并不能决定T应该是什么。其实我想说,Copy方法应该接受一个某种数据类型的List作为第一个参数,一个相同类型的List或者它的基类型的List作为第二个参数。
尽管无法说明一种类型必须是另外一种类型的基类型,但是你可以通过仍旧使用约束机制来克服这一限制。下面是这种方法的实现:
public static void Copy<T, E>(List<t> source,
List<e> destination) where T : E
在此,我已指定类型T必须和E属同一种类型或者是E的子类型。我们很幸运。为什么?T和E在这里都定义了!我们能够指定这种约束(然而,C#中并不鼓励当E也被定义的时候使用E来定义对T的约束)。
然而,请考虑下列的代码:
public class MyList<t>
{
public void CopyTo(MyList<t> destination)
{
//…
}
}
我应该能够调用CopyTo:
MyList<apple> appleList = new MyList<apple>();
MyList<apple> appleList2 = new MyList<apple>();
//…
appleList.CopyTo(appleList2);
我也必须这样做:
MyList<apple> appleList = new MyList<apple>();
MyList<fruit> fruitList2 = new MyList<fruit>();
//…
appleList.CopyTo(fruitList2);
这当然不会成功。如何修改呢?我们说,CopyTo()的参数可以是某种类型的MyList或者是这种类型的基类型的MyList。然而,约束机制不允许我们指定一个基类型。下面情况又该如何呢?
public void CopyTo<e>(MyList<e> destination) where T : E
抱歉,这并不工作。它将给出一个编译错误:
Error 1 ’TestApp.MyList<t>.CopyTo<e>()’ does not define type
parameter ’T’
当然,你可以把代码写成接收任意类型的MyList,然后在代码中,校验该类型是可以接收的类型。然而,这把检查工作推到了运行时刻,丢掉了编译时类型安全的优点。
14. 结论
.NET 2.0中的泛型是强有力的,你写的代码不必限定于一特定类型,然而你的代码却能具有类型安全性。泛型的实现目标是既提高程序的性能又不造成代码的臃肿。然而,在它的约束机制存在不足(无法指定一类型必须是另外一种类型的基类型)的同时,该约束机制也给你书写代码带来很大的灵活性,因为你不必拘泥于各种类型的"最小公分母"能力。