科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件深入浅出.NET泛型编程

深入浅出.NET泛型编程

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

.NET 2.0中泛型的出现是一个令人激动的特征。但是,什么是泛型?你需要它们吗?你会在自己的应用软件中使用它们?在本文中,我们将回答这些问题并细致地分析泛型的使用,能力及其局限性。

作者:朱先忠 来源:天极网 2007年11月4日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
7. 无限制的类型参数

  如果你创建一个泛型数据结构或类,就象例3中的MyList,注意其中并没有约束你该使用什么类型来建立参数化类型。然而,这带来一些限制。如,你不能在参数化类型的实例中使用象==,!=或<等运算符,如:

if (obj1 == obj2) …

  象==和!=这样的运算符的实现对于值类型和引用类型都是不同的。如果随意地允许之,代码的行为可能很出乎你的意料。另外一种限制是缺省构造器的使用。例如,如果你编码象new T(),会出现一个编译错,因为并非所有的类都有一个无参数的构造器。如果你真正编码象new T()来创建一个对象,或者使用象==和!=这样的运算符,情况会是怎样呢?你可以这样做,但首先要限制可被用于参数化类型的类型。读者可以自己先考虑如何实现之。

  8. 约束机制及其优点

  一个泛型类允许你写自己的类而不必拘泥于任何类型,但允许你的类的使用者以后可以指定要使用的具体类型。通过对可能会用于参数化的类型的类型施加约束,这给你的编程带来很大的灵活性--你可以控制建立你自己的类。让我们分析一个例子:

  例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接口并且必须有一个无参构造器。

  9. 继承与泛型

  一个使用参数化类型的泛型类,象MyClass1<T>,称作开放结构的泛型。一个不使用参数化类型的泛型类,象MyClass1<int>,称作封闭结构的泛型。

  你可以从一个封闭结构的泛型进行派生;也就是说,你可以从另外一个称为MyClass1的类派生一个称为MyClass2的类,就象:

public class MyClass2<T> : MyClass1<int>

  你也可以从一个开放结构的泛型进行派生,如果类型被参数化的话,如:

public class MyClass2<T> : MyClass2<T>

  是有效的,但是

public class MyClass2<T> : MyClass2<Y>

  是无效的,这里Y是一个被参数化的类型。非泛型类可以从一个封闭结构的泛型类进行派生,但是不能从一个开放结构的泛型类派生。即:

public class MyClass : MyClass1<int>

  是有效的, 但是

public class MyClass : MyClass1<T>

  是无效的。

  10. 泛型和可代替性

  当我们使用泛型时,要小心可代替性的情况。如果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的类。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章