一、引言
在《The C++ Programming Language》一书中,Bjarne Stroustrop讨论了模板方法在C语言中的伪实现-通过使用预处理和宏来模拟。Stroustrop创建了这种灵活运用C语言的模板和宏的能力,它 使得模板成为用C语言创建类(现在的C++)的相当成熟的一部分。另外的优点是,模板是由编译器进行类型检查的,而不是简单地通过预处理器进行文本替换。
.net 2.0支持称为泛型的模板。其基本概念是相同的:用一个或多个方法来定义一个方法或类,并指定数据类型作为一个可替代的元素。习惯情况下,都使用大写字符 T。最后,当你使用该方法或类时,指明一种类型给泛型T,然后编译器将基于那种类型使用新的信息来生成一个新的且唯一的方法或类。这种新的元素变成了一个 完整的独特的代码块,这完全得益于编译器的处理能力。
本文将分析随着.NET 2.0一起发布的泛型方法和类的定义以及泛型类的使用问题。
二、定义泛型方法
.NET 2.0的泛型化能力可以定义到单一方法这样的粒度。解决的方法是把数据类型与算法独立开来并参数化该数据类型。每一次用不同的数据类型对该方法的参照引用 都产生一个不同的方法。泛型方法还支持约束和重载。一个泛型方法约束是一个新出现的语言特征,它可以把一个类型约束添加到泛型类型上去;这样以来就限制了 泛型参数的数据类型。因而,可以根据泛型参数的存在情况,对方法进行重载。(接下来的几个例子将分析重载的泛型和非泛型方法。)
泛型 的典型用法是把数据类型与通用算法分离开来,一个正规的例子就是把排序算法转化成泛型排序算法。一个非常直接的例子是参数化一个Swap方法。列表1向你 展示怎样定义一个泛型Swap方法-该方法交换两种任何类型的参数。它包含一个表明怎样调用泛型方法的控制台应用程序:
列表1:怎样调用泛型方法
注意:在这个例子中,你也能把一个对象数据类型使用于该Swap方法,因为所有的.NET类型共享一个通用的类型。
需要定义一个泛型方法的新的元素有圆括号,Of关键字以及一个描述泛型类型的参数,在方法名Swap的后面的字符(Of T)正是如此。之后,每当使用该泛型类型,都要使用参数化参数(在本例中是T)。
Swap是一个泛型方法,它有两个可替换的参数和一个可替换的局部变量。例如,Swap(Of Integer)有效地实现了一个有两个整数参数和一个定义为整数的临时变量的Swap方法。
1. 添加一个泛型方法约束
假定你想约束列表1中Swap方法中的类型成为非null的结构类型。你可以添加一个约束到Swap上去以指明Swap只可用于值类型(结构)。列表2显示的Swap方法被约束到结构(或值类型)上。最终结果,在列表1中的Swap(Of String)无法运行:
列表2:带有约束的Swap方法把Swap方法限制到值类型:
参数化类型可以被限制到结构、类、基类、接口以及有缺省构造器(如没有参数的Sub New)的类型。列表2中的加粗的As谓词展示了怎样约束参数化的类型。
泛型支持定义多个参数化类型,而每个参数化类型可以没有或者有多个约束。
2. 重载泛型方法
你可以基于一些参数对方法进行重载,但是没有返回类型,而且方法也能够被通过参数化类型所重载。例如,下面所有这些形式都可以存在于同一个范围之内:
Sub Foo
Sub Foo(ByVal s As String)
Sub Foo(Of T)( ByVal arg As T)
Sub Foo(Of T, U)(ByVal arg1 As T, ByVal arg2 as U)
三、定义一个泛型类
泛型类与普通类一样定义在相同的实例中,只有一个区别。当你有一些数据和多 于一个方法并且其间有关密切的关系时,你可以定义一个普通类。当方法和数据工作在一个密切的单元中并且该数据可以被进一步抽象,以至相同的代码可以支持许 多数据类型时,你最好定义一个泛型类。例如,队列、列表和堆栈并不在乎它们存储的东西,而仅在乎怎样存储这些东西。如果你使用一个其中存储有对象的队列、 堆栈或列表,那么你不得不在你的代码的很多地方进行繁琐的类型转化。如果你使用一个泛型队列、堆栈或列表,那么类型转化只是发生在该类内部。这就是说,繁 琐的类型转化场所集中于该类的一个内部点上,而该类的用户可以依赖编译器来进行类型检查,并不要求用户执行if条件检查和类型变换。
定义一个泛型类就象定义多个泛型方法,只是多了一点:(Of T)构造也可以用于类的头部。为了说明问题,假定你已定义了一个派生于System.Collections.CollectionBase的泛型的强类 型集合(见列表3)。那么现在,你可以把这个类使用于任何数据类型,就好象你已针对所有类型定义了一个定制的类型化的集合:
列表3:一个泛型的强类型集合
如果你读过以前关于类型化的集合的文章,那么你会看到类型化的集合泛型(列表3中的粗体部分)基本上是一种数据类型被参数化的强类型的集合。
四、使用预定义的泛型类
幸好,你不需要从头开始定义泛型类。System.Collections.Generics命名空间已定义好了许多典型的数据结构用作泛型,例如 List,Queue和Stack。你仅需要简单地导入该命名空间并声明一个你需要的类型的实例即可。例如,下列代码充分地实现用 .net 2.0泛型List类型化的集合来替换你的定制的类型化的集合:
作为一个一般规则,如果你想要存储多于一个类型(异类类型),可以使用更旧的风格类,如Queue和Stack。如果你只想使用一种类型(同类类型), 可以使用在System.Collections.Generic命名空间中的新的泛型类。通常情况下,你应使用新的泛型类。
五、选择所学
能够把传统的复杂的如 C++这样的语言与传统的简单的如 VB这 样的语言隔离开来的东西越来越少了。乍看起来,这种事实有点令人心灰意冷,因为这就意味着如今的VB越发难学了-就象C++一样。不过事实上,任何一种语 言(包括VB)的核心往往是非常相似的;而即使象C++这样的语言,你也总可以选择学习如泛型这样更高级的概念,如果你需要它们的话。总之,你可以选择任 何你想学的东西。
还应记住,你总是可以选择学习任何编程相关的高级概念-开始作为一个使用者-使用其中的内容,例如泛型List;然后作为一个创建者,学习怎样创建你自己的东西。总而言之,想短时间内学精每一件技术可能无法实现,而且也完全没有必要。
在《The C++ Programming Language》一书中,Bjarne Stroustrop讨论了模板方法在C语言中的伪实现-通过使用预处理和宏来模拟。Stroustrop创建了这种灵活运用C语言的模板和宏的能力,它 使得模板成为用C语言创建类(现在的C++)的相当成熟的一部分。另外的优点是,模板是由编译器进行类型检查的,而不是简单地通过预处理器进行文本替换。
.net 2.0支持称为泛型的模板。其基本概念是相同的:用一个或多个方法来定义一个方法或类,并指定数据类型作为一个可替代的元素。习惯情况下,都使用大写字符 T。最后,当你使用该方法或类时,指明一种类型给泛型T,然后编译器将基于那种类型使用新的信息来生成一个新的且唯一的方法或类。这种新的元素变成了一个 完整的独特的代码块,这完全得益于编译器的处理能力。
本文将分析随着.NET 2.0一起发布的泛型方法和类的定义以及泛型类的使用问题。
二、定义泛型方法
.NET 2.0的泛型化能力可以定义到单一方法这样的粒度。解决的方法是把数据类型与算法独立开来并参数化该数据类型。每一次用不同的数据类型对该方法的参照引用 都产生一个不同的方法。泛型方法还支持约束和重载。一个泛型方法约束是一个新出现的语言特征,它可以把一个类型约束添加到泛型类型上去;这样以来就限制了 泛型参数的数据类型。因而,可以根据泛型参数的存在情况,对方法进行重载。(接下来的几个例子将分析重载的泛型和非泛型方法。)
泛型 的典型用法是把数据类型与通用算法分离开来,一个正规的例子就是把排序算法转化成泛型排序算法。一个非常直接的例子是参数化一个Swap方法。列表1向你 展示怎样定义一个泛型Swap方法-该方法交换两种任何类型的参数。它包含一个表明怎样调用泛型方法的控制台应用程序:
列表1:怎样调用泛型方法
Module Module1 Sub Main() Dim I As Integer = 5 Dim J As Integer = 7 Swap(Of Integer)(I, J) Console.WriteLine("I = " & I) Console.WriteLine("J = " & J) Dim S As String = "Paul" Dim R As String = "Lori" Swap(Of String)(S, R) Console.WriteLine("S = " & S) Console.WriteLine("R = " & R) Console.ReadLine() End Sub Public Sub Swap(Of T)(ByRef a As T, ByRef b As T) Dim temp As T temp = a a = b b = temp End Sub End Module |
注意:在这个例子中,你也能把一个对象数据类型使用于该Swap方法,因为所有的.NET类型共享一个通用的类型。
需要定义一个泛型方法的新的元素有圆括号,Of关键字以及一个描述泛型类型的参数,在方法名Swap的后面的字符(Of T)正是如此。之后,每当使用该泛型类型,都要使用参数化参数(在本例中是T)。
Swap是一个泛型方法,它有两个可替换的参数和一个可替换的局部变量。例如,Swap(Of Integer)有效地实现了一个有两个整数参数和一个定义为整数的临时变量的Swap方法。
1. 添加一个泛型方法约束
假定你想约束列表1中Swap方法中的类型成为非null的结构类型。你可以添加一个约束到Swap上去以指明Swap只可用于值类型(结构)。列表2显示的Swap方法被约束到结构(或值类型)上。最终结果,在列表1中的Swap(Of String)无法运行:
列表2:带有约束的Swap方法把Swap方法限制到值类型:
Public Sub Swap(Of T As Structure)(ByRef a As T, ByRef b As T) Dim temp As T temp = a a = b b = temp End Sub |
参数化类型可以被限制到结构、类、基类、接口以及有缺省构造器(如没有参数的Sub New)的类型。列表2中的加粗的As谓词展示了怎样约束参数化的类型。
泛型支持定义多个参数化类型,而每个参数化类型可以没有或者有多个约束。
2. 重载泛型方法
你可以基于一些参数对方法进行重载,但是没有返回类型,而且方法也能够被通过参数化类型所重载。例如,下面所有这些形式都可以存在于同一个范围之内:
Sub Foo
Sub Foo(ByVal s As String)
Sub Foo(Of T)( ByVal arg As T)
Sub Foo(Of T, U)(ByVal arg1 As T, ByVal arg2 as U)
三、定义一个泛型类
泛型类与普通类一样定义在相同的实例中,只有一个区别。当你有一些数据和多 于一个方法并且其间有关密切的关系时,你可以定义一个普通类。当方法和数据工作在一个密切的单元中并且该数据可以被进一步抽象,以至相同的代码可以支持许 多数据类型时,你最好定义一个泛型类。例如,队列、列表和堆栈并不在乎它们存储的东西,而仅在乎怎样存储这些东西。如果你使用一个其中存储有对象的队列、 堆栈或列表,那么你不得不在你的代码的很多地方进行繁琐的类型转化。如果你使用一个泛型队列、堆栈或列表,那么类型转化只是发生在该类内部。这就是说,繁 琐的类型转化场所集中于该类的一个内部点上,而该类的用户可以依赖编译器来进行类型检查,并不要求用户执行if条件检查和类型变换。
定义一个泛型类就象定义多个泛型方法,只是多了一点:(Of T)构造也可以用于类的头部。为了说明问题,假定你已定义了一个派生于System.Collections.CollectionBase的泛型的强类 型集合(见列表3)。那么现在,你可以把这个类使用于任何数据类型,就好象你已针对所有类型定义了一个定制的类型化的集合:
列表3:一个泛型的强类型集合
Module Module1 Sub Main() Dim BrokenBones As TypedCollection(Of OrthoInjury) = New TypedCollection(Of OrthoInjury) BrokenBones.Add(New OrthoInjury(True,"Broken Right Clavicle", "Vicodin; Heals n 8 to 12 weeks")) BrokenBones.Add(New OrthoInjury(True, "Fractured Posterior Rib #5", "Heals in 6 to 8 weeks")) BrokenBones.Add(New OrthoInjury(True, "Fractured Posterior Rib #1", "Heals in 6 to 8 weeks")) Dim injury As OrthoInjury For Each injury In BrokenBones Console.WriteLine("Description: " & injury.Description) Next Console.ReadLine() End Sub End Module Public Class TypedCollection(Of T) Inherits System.Collections.CollectionBase Default Public Property Item(ByVal Index As Integer) As T Get Return CType(List(Index), T) End Get Set(ByVal value As T) List(Index) = value End Set End Property Public Function Add(ByVal value As T) As Integer Return List.Add(value) End Function End Class Public Class OrthoInjury Private FHasXray As Boolean Private FDescription As String Private FPrognosis As String Public Sub New(ByVal HasXray As Boolean, ByVal Description As String, ByVal Prognosis As String) FHasXray = HasXray FDescription = Description FPrognosis = Prognosis End Sub Public Property HasXray() As Boolean Get Return FHasXray End Get Set(ByVal value As Boolean) FHasXray = value End Set End Property Public Property Description() As String Get Return FDescription End Get Set(ByVal value As String) FDescription = value End Set End Property Public Property Prognosis() As String Get Return FPrognosis End Get Set(ByVal value As String) FPrognosis = value End Set End Property End Class |
如果你读过以前关于类型化的集合的文章,那么你会看到类型化的集合泛型(列表3中的粗体部分)基本上是一种数据类型被参数化的强类型的集合。
四、使用预定义的泛型类
幸好,你不需要从头开始定义泛型类。System.Collections.Generics命名空间已定义好了许多典型的数据结构用作泛型,例如 List,Queue和Stack。你仅需要简单地导入该命名空间并声明一个你需要的类型的实例即可。例如,下列代码充分地实现用 .net 2.0泛型List类型化的集合来替换你的定制的类型化的集合:
Dim BrokenBones As System.Collections.Generic.List(Of OrthoInjury) = _ New System.Collections.Generic.List(Of OrthoInjury) |
作为一个一般规则,如果你想要存储多于一个类型(异类类型),可以使用更旧的风格类,如Queue和Stack。如果你只想使用一种类型(同类类型), 可以使用在System.Collections.Generic命名空间中的新的泛型类。通常情况下,你应使用新的泛型类。
五、选择所学
能够把传统的复杂的如 C++这样的语言与传统的简单的如 VB这 样的语言隔离开来的东西越来越少了。乍看起来,这种事实有点令人心灰意冷,因为这就意味着如今的VB越发难学了-就象C++一样。不过事实上,任何一种语 言(包括VB)的核心往往是非常相似的;而即使象C++这样的语言,你也总可以选择学习如泛型这样更高级的概念,如果你需要它们的话。总之,你可以选择任 何你想学的东西。
还应记住,你总是可以选择学习任何编程相关的高级概念-开始作为一个使用者-使用其中的内容,例如泛型List;然后作为一个创建者,学习怎样创建你自己的东西。总而言之,想短时间内学精每一件技术可能无法实现,而且也完全没有必要。