技术开发 频道

深入浅出.NET泛型编程(下)

  泛型与反射
 
  既然泛型是在CLR级上得到支持的,你可以使用反射API来取得关于泛型的信息。如果你是编程的新手,可能有一件事让你疑惑:你必须记住既有你写的泛型类也有在运行时从该泛型类创建的类型。因此,当使用反射API时,你需要另外记住你在使用哪一种类型。我将在例7说明这一点:

  例7.在泛型上的反射

public class MyClass<t> { } class Program {  static void Main(string[] args)  {   MyClass<int> obj1 = new MyClass<int>();   MyClass<double> obj2 = new MyClass<double>();   Type type1 = obj1.GetType();   Type type2 = obj2.GetType();   Console.WriteLine("obj1’s Type");   Console.WriteLine(type1.FullName);   Console.WriteLine(type1.GetGenericTypeDefinition().FullName);   Console.WriteLine("obj2’s Type");   Console.WriteLine(type2.FullName);   Console.WriteLine(type2.GetGenericTypeDefinition().FullName);  } }
  在本例中,有一个MyClass<int>的实例,程序中要查询该实例的类名。然后我查询这种类型的GenericTypeDefinition()。GenericTypeDefinition()会返回MyClass<T>的类型元数据。你可以调用IsGenericTypeDefinition来查询是否这是一个泛型类型(象MyClass<T>)或者是否已指定它的类型参数(象MyClass<int>)。同样地,我查询MyClass<double>的实例的元数据。上面的程序输出如下:
 
obj1s Type
TestApp.MyClass`1
[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]]
TestApp.MyClass`1
obj2s Type
TestApp.MyClass`1
[[System.Double, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]]
TestApp.MyClass`1
 
  可以看到,MyClass<int>和MyClass<double>是属于mscorlib配件集的类(动态创建的),而类MyClass<t>属于我自建的配件集。
 
  泛型的局限性
 
  至此,我们已了解了泛型的强大威力。是否其也有不足呢?我发现了一处。我希望微软能够明确指出泛型存在的这一局制性。在表达约束的时候,我们能指定参数类型必须继承自一个类。然而,指定参数必须是某种类的基类型该如何呢?为什么要那样做呢?
 
  在例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,然后在代码中,校验该类型是可以接收的类型。然而,这把检查工作推到了运行时刻,丢掉了编译时类型安全的优点。
 
  结论
.NET 2.0中的泛型是强有力的,你写的代码不必限定于一特定类型,然而你的代码却能具有类型安全性。泛型的实现目标是既提高程序的性能又不造成代码的臃肿。然而,在它的约束机制存在不足(无法指定一类型必须是另外一种类型的基类型)的同时,该约束机制也给你书写代码带来很大的灵活性,因为你不必拘泥于各种类型的最小公分母能力。

 

 

 

0
相关文章