分享

深入浅出.NET泛型编程

 kittywei 2011-10-25

【IT168 技术文档】

泛型方法
 
  除了有泛型类,你也可以有泛型方法。泛型方法可以是任何类的一部分。

例4.一个泛型方法
public class Program {  public static void Copy<T>(List<T> source, List<T> destination)  {   foreach (T obj in source)   {    destination.Add(obj);   }  }  static void Main(string[] args)  {   List<int> lst1 = new List<int>();   lst1.Add(2);   lst1.Add(4);   List<int> lst2 = new List<int>();   Copy(lst1, lst2);   Console.WriteLine(lst2.Count);  } }
  Copy()方法就是一个泛型方法,它与参数化的类型T一起工作。当在Main()中激活Copy()时,编译器根据提供给Copy()方法的参数确定出要使用的具体类型。
 
无限制的类型参数
 
  如果你创建一个泛型数据结构或类,就象例3中的MyList,注意其中并没有约束你该使用什么类型来建立参数化类型。然而,这带来一些限制。如,你不能在参数化类型的实例中使用象==,!=或<等运算符,如:if (obj1 == obj2) …
 
  象==和!=这样的运算符的实现对于值类型和引用类型都是不同的。如果随意地允许之,代码的行为可能很出乎你的意料。另外一种限制是缺省构造器的使用。例如,如果你编码象new T(),会出现一个编译错,因为并非所有的类都有一个无参数的构造器。如果你真正编码象new T()来创建一个对象,或者使用象==和!=这样的运算符,情况会是怎样呢?你可以这样做,但首先要限制可被用于参数化类型的类型。读者可以自己先考虑如何实现之。
 
约束机制及其优点
 
  一个泛型类允许你写自己的类而不必拘泥于任何类型,但允许你的类的使用者以后可以指定要使用的具体类型。通过对可能会用于参数化的类型的类型施加约束,这给你的编程带来很大的灵活性--你可以控制建立你自己的类。让我们分析一个例子:
 
例5.需要约束:代码不会编译成功
public static T Max<T>(T op1, T op2) {  if (op1.CompareTo(op2) < 0)   return op1;  return op2; }
例5中的代码将产生一个编译错误:
 
Error 1 T does not contain a definition for CompareTo
 
  假定我需要这种类型以支持CompareTo()方法的实现。我能够通过加以约束--为参数化类型指定的类型必须要实现IComparable接口--来指定这一点。例6中的代码就是这样:
 
例6.指定一个约束
public static T Max<T>(T op1, T op2) where T : IComparable {  if (op1.CompareTo(op2) < 0)   return op1;  return op2; }
  在例6中,我指定的约束是,用于参数化类型的类型必须继承自(实现)Icomparable。下面的约束是可以使用的:
 
  where T : struct 类型必须是一种值类型(struct)
 
  where T : class 类型必须是一种引用类型(class)
 
  where T : new() 类型必须有一个无参数的构造器
 
  where T : class_name 类型可以是class_name或者是它的一个子类
 
  where T : interface_name 类型必须实现指定的接口
 
  你可以指定约束的组合,就象: where T : IComparable, new()。这就是说,用于参数化类型的类型必须实现Icomparable接口并且必须有一个无参构造器。
 
  继承与泛型
 
  一个使用参数化类型的泛型类,象MyClass1<T>,称作开放结构的泛型。一个不使用参数化类型的泛型类,象MyClass1<int>,称作封闭结构的泛型。
 
  你可以从一个封闭结构的泛型进行派生;也就是说,你可以从另外一个称为MyClass1的类派生一个称为MyClass2的类,就象:public class MyClass2<T> : MyClass1<int>;你也可以从一个开放结构的泛型进行派生,如果类型被参数化的话,如:public class MyClass2<T> : yClass2<T>,是有效的,但是public class MyClass2<T> : MyClass2<Y>是无效的,这里Y是一个被参数化的类型。非泛型类可以从一个封闭结构的泛型类进行派生,但是不能从一个开放结构的泛型类派生。即:public class MyClass : MyClass1<int>是有效的, 但是public class MyClass : MyClass1<T>是无效的。
 
  泛型和可代替  
当我们使用泛型时,要小心可代替性的情况。如果B继承自A,那么在使用对象A的地方,可能都会用到对象B。假定我们有一篮子水果(a Basket of Fruits (Basket<Fruit>)),而且有继承自Fruit的Apple和Banana(皆为Fruit的种类)。一篮子苹果--Basket of Apples (Basket<apple>)可以继承自Basket of Fruits (Basket<Fruit>)?答案是否定的,如果我们考虑一下可代替性的话。为什么?请考虑一个a Basket of Fruits可以工作的方法:
public void Package(Basket<Fruit> aBasket) {  aBasket.Add(new Apple());  aBasket.Add(new Banana()); }
  如果发送一个Basket<Fruit>的实例给这个方法,这个方法将添加一个Apple对象和一个Banana对象。然而,发送一个Basket<Apple>的实例给这个方法时,会是什么情形呢?你看,这里充满技巧。这解释了为什么下列代码:
Basket<Apple> anAppleBasket = new Basket<Apple>(); Package(anAppleBasket);
  会产生错误:
 
  Error 2 Argument 1:
  cannot convert from TestApp.Basket<testapp.apple>
  to TestApp.Basket<testapp.fruit>
 
  编译器通过确保我们不会随意地传递一个集合的派生类(此时需要一个集合的基类),保护了我们的代码。这不是很好吗?

 

 
这在上面的例中在成功的,但也存在特殊情形:有时我们确实想传递一个集合的派生类,此时需要一个集合的基类。例如,考虑一下Animal(如Monkey),它有一个把Basket<Fruit>作参数的方法Eat,如下所示:
public void Eat(Basket<Fruit> fruits) {  foreach (Fruit aFruit in fruits)  {   //将吃水果的代码  } }
  现在,你可以调用:
Basket<Fruit> fruitsBasket = new Basket<Fruit>(); … //添加到Basket对象中的对象Fruit anAnimal.Eat(fruitsBasket);
  如果你有一篮子(a Basket of)Banana-一Basket<Banana>,情况会是如何呢?把一篮子(a Basket of)Banana-一Basket<Banana>发送给Eat方法有意义吗?在这种情形下,会成功吗?真是这样的话,编译器会给出错误信息:
 
Basket<Banana> bananaBasket = new Basket<Banana>();
//…
anAnimal.Eat(bananaBasket);
 
  编译器在此保护了我们的代码。我们怎样才能要求编译器允许这种特殊情形呢?约束机制再一次帮助了我们:
public void Eat<t>(Basket<t> fruits) where T : Fruit {  foreach (Fruit aFruit in fruits)  {   //将吃水果的代码  } }
  在建立方法Eat()的过程中,我要求编译器允许一篮子(a Basket of)任何类型T,这里T是Fruit类型或任何继承自Fruit的类。
 
   泛型和代理
 
   代理也可以是泛型化的。这样就带来了巨大的灵活性。
 
  假定我们对写一个框架程序很感兴趣。我们需要提供一种机制给事件源以使之可以与对该事件感兴趣的对象进行通讯。我们的框架可能无法控制事件是什么。你可能在处理某种股票价格变化(double price),而我可能在处理水壶中的温度变化(temperature value),这里Temperature可以是一种具有值、单位、门槛值等信息的对象。那么,怎样为这些事件定义一接口呢?
 
   让我们通过pre-generic代理技术细致地分析一下如何实现这些:

 

public delegate void NotifyDelegate(Object info); public interface ISource {  event NotifyDelegate NotifyActivity; }
  我们让NotifyDelegate接受一个对象。这是我们过去采取的最好措施,因为Object可以用来代表不同类型,如double,Temperature,等等--尽管Object含有因值类型而产生的装箱的开销。ISource是一个各种不同的源都会支持的接口。这里的框架展露了NotifyDelegate代理和ISource接口。
 
  让我们看两个不同的源码:
public class StockPriceSource : ISource {  public event NotifyDelegate NotifyActivity;  // } public class BoilerSource : ISource {  public event NotifyDelegate NotifyActivity;  // }
  如果我们各有一个上面每个类的对象,我们将为事件注册一个处理器,如下所示:
StockPriceSource stockSource = new StockPriceSource(); stockSource.NotifyActivity += new NotifyDelegate(stockSource_NotifyActivity); //这里不必要出现在同一个程序中 BoilerSource boilerSource = new BoilerSource(); boilerSource.NotifyActivity += new NotifyDelegate(boilerSource_NotifyActivity); 在代理处理器方法中,我们要做下面一些事情: 对于股票事件处理器,我们有: void stockSource_NotifyActivity(object info) {  double price = (double)info;  //在使用前downcast需要的类型 }   温度事件的处理器看上去会是: void boilerSource_NotifyActivity(object info) { Temperature value = info as Temperature; //在使用前downcast需要的类型 }
  上面的代码并不直观,且因使用downcast而有些凌乱。借助于泛型,代码将变得更易读且更容易使用。让我们看一下泛型的工作原理:
 
  下面是代理和接口:
public delegate void NotifyDelegate<t>(T info); public interface ISource<t> {  event NotifyDelegate<t> NotifyActivity; }
  我们已经参数化了代理和接口。现在的接口的实现中应该能确定这是一种什么类型。
 
  Stock的源代码看上去象这样:
public class StockPriceSource : ISource<double> {  public event NotifyDelegate<double> NotifyActivity;  // }
  而Boiler的源代码看上去象这样:
public class BoilerSource : ISource<temperature> {  public event NotifyDelegate<temperature> NotifyActivity;  // }
  如果我们各有一个上面每种类的对象,我们将象下面这样来为事件注册一处理器:
StockPriceSource stockSource = new StockPriceSource(); stockSource.NotifyActivity += new NotifyDelegate<double>(stockSource_NotifyActivity); //这里不必要出现在同一个程序中 BoilerSource boilerSource = new BoilerSource(); boilerSource.NotifyActivity += new NotifyDelegate<temperature>(boilerSource_NotifyActivity);
  现在,股票价格的事件处理器会是:
void stockSource_NotifyActivity(double info) { //… }
  温度的事件处理器是:
void boilerSource_NotifyActivity(Temperature info) { //… }
  这里的代码没有作downcast并且使用的类型是很清楚的。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多