你需要理解.NET类型是怎么用泛型运作的,并且泛型和其他正常的.NET类型有什么区别。
解决方法:
一对实例可以显示其中的区别。当一个正常的类型被定义之后,就像例1中定义的StandardClass类一样。
例1:
StandardClass有一个静态的整数变量_count,它是构造函数中递增的,和一个重载的ToString()函数,它输出在这个“主程序”中有多少个StandardClass的实例。StandardClass同时还包含一个objects(_items)的数组,它的大小是由构造函数的参数items决定的。它实现了增加和获取项的方法和一个只读属性(ItemCount)去获取数组中的项数。
GenericClass<T>类是一个.NET泛型类型的类,其内容和StandardClass类中大同小异。
例2:
当你看到GenericClass<T>类的_items数组的定义的时候,会发现和StandardClass中的定义有所区别:
在这里是:T
[] _items;
而在StandardClass中是:object [] _items;
_items数组用了泛型类的类型参数(<T>)去决定什么类型是可以在_items数组中的。StandardClass类用object作为_items数组的类型,它使得任何类型的数据都可以被保存在这个数组中(因为所有的类型都是从object集成而来的)。然而,GenericClass<T>通过类型参数决定了那种特定的类型是被允许保存在数组中的,从而提供了类型安全。
接下去的不同点,在AddItem和GetItem的方法声明中是显而易见的。AddItem现在接受一个类型T作为参数,而在StandardClass中它用object类型。GetItem方法现在返回一个类型T,而在StandardClass中它返回一个object类型。这些变化使得GenericClass<T>类中的方法可以使用特定的类型去存贮和返回数组中的项,而不是允许任何类型都可以保存在StandardClass类中。
这样有几个优势。第一也是最重要的是GenericClass<T>类为数组中的项提供的类型安全机制。在StandardClass类中,像这样写代码是可能的:
但是,如果你在GenericClass<T>中做同样的事,编译器会给你一个错误:
在它成为运行时的错误源之前,编译器已经预防了这个错误,是一件非常好的事情。
这也许不会被立即发现,但是这个整数确实在它被加到StandardClass类的时候被装箱了。正如你可以在IL中在StandardClass上调用GetItem时看到的那样:
这个装箱活动把int这个值类型转变成了一个引用类型(object),从而保存在数组中。这导致了在保存数据到object数组时的额外操作。
当你从StandardClass类中去取出一项时会有一个问题。看一下StandardClass.GetItem是怎样返回一项的:
因为StandardClass.GetItem返回的项是object类型的,它需要把它转变为一个string从而去得到在序列1中的字符串表达。这不一定是string类型的。所有你确定知道的就是它是一个object类型,但是,你必须把它转变为一个更确定的类型,从而你可以准确地表示它。字符串是一个特例,因为,所有的对象都可以用一个字符串来表示它们自己。但是,你可以想象得到,如果这个数组保存一个小数,却要被转换为布尔值,会成为一个问题。
这些问题都被GenericClass<T>类修复了。拆箱工作不再需要,因为GetItem返回的是特定的类型,并且编译器通过检查返回的值强制这样。
为了看看这两种类型的其他区别,实例化一些各自的实例
这是它们的输出结果:
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[]…
讨论:
在泛型中的参数使得你可以在不知道最终你要使用的类型之前创建类型安全的代码。在很多实例中,你希望这个类型有特定的属性,在这种情况下,你在类型上设置限制。即使类本身没有泛型参数的时候,方法同样也可以有泛型类型参数(在后序的文章中会说明这一点)。
注意,StandardClass有三个实例,而GenericClass的一个实例,是用<bool>声明作为它的类型的;一个是<int>类型的,两个是<string>类型的。这意味着,当有一个.NET类型的对象被创建为非泛型类时,就有一个泛型类的特定类型的.NET类型对象被创建。
在示例中StandardClass有三个实例,因为StandardClass只有一种被CLR维护的类型。用泛型,每一个类型都是它的类模板和传递给构造函数的类型参数的组合体所维护的。为了使它更清晰,你得到了一个GenericClass<bool>.NET类型,一个GenericClass<int>.NET类型,一个GenericClass<string>.NET类型。
内部的静态成员变量帮助我们去解释这一点,因为一个类的静态变量是和CLR保持的类型想关联的。CLR创建一次任何给定的类型,并且维护它们直到主程序退出为止。这是为什么ToString()函数返回的是3个StandardClass和1到2个GenericClass<T>类型。