分类: C/C++
2009-11-02 17:44:17
本系列文章翻译O'Reilly 出版的《C# Cookbook》一书中的片段,仅供学习交流使用
4.0 介绍
泛型,一个期待已久的功能,随着C# 2.0版本编译器的到来最终出现。泛型是一个非常有用的功能,它使得您的代码变得精简而富有效率。这些将在秘诀4.1进行详细讲述。泛型的到来使得您可以编写更为强大的应用程序,但这需要正确地使用它。如果您考虑把ArrayList,Queue,Stack和Hashtable对象转变为使用相应的泛型版本,可以阅读秘诀4.4,4.5和4.10。当您阅读过后,会发现这种转变不一定简单,甚至有可能会不再打算进行转变。
本章的另外一些秘诀涉及到.NET Framework 2.0所包含的其他泛型类,如秘诀4.6。其他秘诀讲述一些泛型类的操作,如秘诀4.2,4.8和4.13。
4.1决定在何时何地使用泛型
问题
您希望在一个新工程内使用泛型,或者想把已有项目中的非泛型类转换为等价的泛型版本。但您并非了解为何要这样做,也不知道哪个非泛型类应该被转换为泛型类。
解决方案
决定在何时何地使用泛型,您需要考虑以下几件事件:
l 您所使用的类型是否包含或操作未指定的数据类型(如集合类型)?如果是这样,如果是这样,创建泛型类型将能提供更多的好处。如果您的类型只操作单一的指定类型,那么就没有必要去创建一个泛型类。
l 如果您的类型将操作值类型,那么就会产生装箱和拆箱操作,就应该考虑使用泛型来防止装箱和拆箱操作。
l 泛型的强类型检查有助于快速查找错误(也就是编译期而非运行期),从而缩短bug修复周期。
l 在编写多个类操作多个数据类型时是否遭遇到“代码膨胀”问题(如一个ArrayList只存储StreamReaders而另一个存储StreamWriters)?其实编写一次代码并让它工作于多个数据类型非常简单。
l 泛型使得代码更为清晰。通过消除代码膨胀并进行强制检查,您的代码将变得更易于阅读和理解。
讨论
很多时候,使用泛型类型将使您受益。泛型将使得代码重用更有效率,具有更快的执行速度,进行强制类型检查,获得更易读的代码。
阅读参考
MSDN文档中的“Generics Overview”和“Benefits of Generics”主题。
4.2 理解泛型类型
问题
您需要理解泛型类型在.NET中是如何工作的,它跟一般的.NET类型有什么不同。
解决方案
几个小实验就可以演示一般类型和泛型类型之间的区别。例4-1中的StandardClass类就是一个般类型。
例4-1 StandardClass:一般的.NET类型
StandardClass类有一个整型静态成员变量_count,用于在实例构造器中计数。重载的ToString()方法打印在这个应用程序域中StandardClass类实例的数目。StandardClass类还包括一个object数组(_item),它的长度由构造方法中的传递的参数来决定。它实现了添加和获得项的方法(AddItem,GetItem),还有一个只读属性来获取数组中的项的数目(ItemCount)。
GenericClass
Example4-2 GenericClass
从GenericClass
T[] _items;
而不是
object[] _items;
_items数组使用泛型类(
下一个不同在于AddItem和GetItem方法的声明。AddItem现在使用一个类型T做为参数,而在StandardClass中使用object类型做为参数。GetItem现在的返回值类型T,StandardClass返回值为object类型。这个改变允许GenericClass
这样做的优势在于,首先通过GenericClass
编译器防止它成为运行时源码的bug,这是一件非常美妙的事情。
虽然并非显而易见,但在StandardClass中把整数添加进object数组会导致装箱操作,这一点可以StandardClass调用GetItem方法时的IL代码:
IL_0170: ldloc.2
IL_0171: ldloc.s i1
IL_0173: box [mscorlib]System.Int32
IL_0178: callvirt instance int32 CSharpRecipes.Generics/StandardClass::AddItem(object)
这个装箱操作把做为值类型的整数转换为引用类型(object),从而可以在数组中存储。这导致了在object数组中存储值类型时需要增加额外的工作。
当您在运行StandardClass并从类中返回一个项时,还会产生一个问题,来看看StandardClass.GetItem如何返回一个项:
// 存储返回的字符串.
string sHolder;
// 发生错误CS0266:
// Cannot implicitly convert type 'object' to 'string'…
sHolder = (string)C.GetItem(1);
因为StandardClass.GetItem返回的是object类型,而您希望通过索引1获得一个字符串类型,所以需要把它转换为字符串类型。然而它有可能并非字符串-----只能确定它是一个object-----但为了赋值正确,您不得不把它转换为更明确的类型。字符串比较特殊,所有对象都可以自行提供一个字符串描述,但当数组接收一个double类型并把它赋给一个布尔类型就会出问题。
这两个问题在GenericClass
string sHolder;
int iHolder;
// 不需要再进行转换
sHolder = gC.GetItem(1);
// 尝试把字符串变为整数.将出现
// 错误CS0029: Cannot implicitly convert type 'string' to 'int'
//iHolder = gC.GetItem(1);
为了了解两种类型的其他不同点,分别给出它们的示例代码:
上述代码输出结果如下:
There are 1 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...
There are 2 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...
There are 3 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...
There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.Boolean] which contains 0 items of type System.Boolean[]...
There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.Int32] which contains 0 items of type System.Int32[]...
There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.String] which contains 0 items of type System.String[]...
There are 2 instances of CSharpRecipes.Generics+GenericClass`1[System.String] which contains 0 items of type System.String[]...
讨论
泛型中的类型参数允许您在不知道使用何种类型的情况下提供类型安全的代码。在很多场合下,您希望类型具有某些指定的特征,这可以通过使用类型约束(秘诀4.12)来实现。方法在类本身不是泛型的情况下也可以拥有泛型类型的参数。秘诀4.9为此演示了一个例子。
注意当StandardClass拥有三个实例,GenericClass有一个声明为
示例代码中的StandardClass类有三个实例,因为CLR中只维护一个StandardClass类型。而在泛型中,每种类型都被相应的类型模板所维护,当创建一个类型实例时,类型实参被传入。说得更清楚一些就是为GenericClass
内部静态成员_count可以帮助说明这一点,一个类的静态成员实际上是跟CLR中的类型相连的。CLR对于给定的类型只会创建一次,并维护它一直到应用程序域卸载。这也是为什么在调用ToString方法时,输出显示有StandardClass的三个实例,而GenericClass
阅读参考
MSDN文档中的“Generic Type Parameters”和“Generic Classes”主题。