反射和泛型

于知道我热衷此道,一个朋友最近请我帮助她编写一个简单的工具,将她需要记录的新程序集里所有成员的可排序列表导出到 Microsoft ® Excel ® 中。该工具需要提供关于属性、方法、事件和接口的信息,并列出每个成员的所有参数。我以前研究过反射,所以觉得这很有趣。

 

即便如此,从一开始,我就一直发现格式古怪的成员名称。例如,使用我的程序列出 mscorlib 程序集的内容时,我发现类似 Dictionary`2 和 ReadOnlyCollection`1 的名称。

出现这样的名称肯定有其原因。Microsoft .NET Framework 公开有关泛型类型和方法的信息与公开非泛型的信息不同。基于这一认识,我开始研究(现在开始记录)与泛型相关的反射特性。虽然泛型是 .NET Framework 2.0 中新增的,使用反射来调查、编制或调用泛型类型和方法却不是特别困难。我的示例应用程序(可从《MSDN® 杂志》网站获得)展示了我所要讨论的有关泛型和反射的诸多特性。

如果您完全不了解泛型,请参阅 msdn2.microsoft.com/ms172193 上的“.NET Framework 中的泛型概述”。最重要的是重温一下文章中阐述的术语,因为我的目标是向您展示如何扩展基于反射的现有应用程序,使其可以处理泛型类型和方法。


开始调查

给您一个程序集,您能够很容易地使用反射来调查其成员。您需要使用 Type.IsGenericType 属性来了解每个类型是否为泛型,然后才能创建类型的实例。在我的示例中,TestForGenericType 过程加载 mscorlib 程序集的内容,并显示所有公共泛型类型的相关信息:

 

Dim asm As Assembly = Assembly.ReflectionOnlyLoad("mscorlib")
For Each typ As Type In asm.GetTypes()
    If typ.IsPublic AndAlso typ.IsGenericType Then
        AddToResults(typ.Name)
    End If
Next

 

示例中的 AddToResults 方法和 DisplayResults 方法(此处未显示)只是构建输出文本并通过警报显示该文本。这样您就可以调查您所创建的类型了,示例中还包括一个 TestClasses.vb 文件,该文件包含几个示例类,如图 1 所示。

TestForGenericType 接下来测试这三个类:

 

AddToResults("GenericClass(Of Integer) is generic: {0}", _
  GetType(GenericClass(Of Integer)).IsGenericType)
AddToResults("NonGenericClass is generic: {0}", _
  GetType(NonGenericClass).IsGenericType)
AddToResults( _
  "BaseClass(Of Integer, String) is generic: {0}", _
  GetType(BaseClass(Of Integer, String)). _
  IsGenericType)

 

此代码创建了上文所示的每个泛型类型的构造版本。(构造类型是一种已经为每个占位符类型提供了具体类型的泛型类型。)GenericClass 和 BaseClass 是泛型类型,NonGenericClass 则不是。注意,当 .NET Framework 返回泛型类型的 Name 属性时,会在其尾部添加泛型参数的个数。这就是 Dictionary 类显示为 Dictionary`2 的原因。

Back to top

调查方法

您可以对方法重复同一种试验。若要动态执行一个方法,您需要知道该方法是否为泛型,以便构造一个方法的版本,而您在该版本中满足了它所有的类型占位符。如果一个方法包括如下两组类型参数,就可视为泛型:一组参数从整体上描述该方法,另一组则提供该方法的参数。例如,在 GenericClass 中,您会发现 Swap 方法的两个重载版本。第一个版本不是泛型——其参数类型由类的类型占位符定义。第二个版本才是泛型。在本例中,编译器依据该过程的第一个参数的类型推断出占位符的类型。(如果您试图调用该过程,传入两个不同的类型,编译器就会报错。)TestForGenericMethod 过程使用 MethodInfo.IsGenericMethod 属性来判定一个方法是否为泛型,并调用下列代码测试该属性:

 

Dim typ As Type = GetType(GenericClass(Of Integer))

For Each method As MethodInfo In typ.GetMethods( _
  BindingFlags.Public Or BindingFlags.Instance _
  Or BindingFlags.DeclaredOnly)

  AddToResults(method.Name & " is generic: {0}", _
   method.IsGenericMethod)
Next

 

此代码在对 Type.GetMethods 方法的调用中使用了 BindingFlags 枚举,以便仅检索直接在类中声明的公共实例方法。输出如下所示:

 

Swap is generic: True
GenericMethod is generic: True
Swap is generic: False

 

Back to top

创建类型的实例

如果要创建一个类型的实例,则需要知道它是一个泛型类型定义还是一个构造泛型类型。泛型类型定义有一个或多个未满足的类型占位符,换句话说,泛型类型定义不知道它自己的参数类型。构造泛型类型(如前面示例中所示)的类型占位符已替换为具体类型。您无法创建非构造泛型类型的实例,在试图创建实例前,您必须提供具体类型。使用 Type.IsGenericTypeDefinition 属性可判定类型的状态,如图 2 中 TestForGenericTypeDefinition 过程的代码所示。

您已经了解了如何检索一个构造泛型类型对应的 Type 对象。您还可以对泛型类型定义采用同样的做法,如图 2 的第一行代码所示(请务必在每个所需类型占位符之间插入一个逗号分隔符):

 

Dim type1 As Type = GetType(Dictionary(Of ,))

 

对于给定的构造类型,可以使用 Type.GetGenericTypeDefinition 方法检索其泛型类型的定义。下列代码可创建泛型 Dictionary 类的一个构造实例,检索其类型,然后检索构造泛型类型的泛型类型定义:

 

Dim type2 As New Dictionary(Of String, DateTime)
Dim type3 As Type = type2.GetType()
Dim type4 As Type = type3.GetGenericTypeDefinition()

 

最后,该过程比较 type4 和 type1 中的引用;它们应该是完全相同的引用(即 Dictionary 实例的泛型类型定义)。该过程的输出如下所示,表明 type1 和 type4 是相同的泛型类型定义:

 

type1 is generic type definition: True
type3 is generic type definition: False
type4 is generic type definition: True

type1 is type4: True

 

Type3 是构造泛型类型,而不是泛型类型定义。

您还可以采用类似的方法来判定所调查的类型究竟是标准类型、泛型类型,还是泛型类型定义。下列代码演示了 MethodInfo.IsGenericMethod 和 MethodInfo.IsGenericMethodDefinition 属性:

 

Dim type1 As Type = GetType(GenericClass(Of ))

Dim method As MethodInfo = type1.GetMethod("GenericMethod")
If method.IsGenericMethodDefinition Then
    Dim miConstructed As MethodInfo = _
        method.MakeGenericMethod(GetType(Integer))
    AddToResults("Is generic method definition: {0}", _
        miConstructed.IsGenericMethodDefinition)
    AddToResults("Is generic method: {0}", _
        miConstructed.IsGenericMethod)
End If

 

每个泛型方法定义也是一个泛型方法,但是反之则不然;每个泛型方法并不是一个泛型方法定义。如果您为一个方法提供了类型占位符的值,那么,它就是泛型方法,而不是泛型方法定义。此示例还演示了 MethodInfo.MakeGenericMethod 方法,利用该方法,可以为一个泛型方法定义的类型占位符提供类型,从而依据定义构造一个泛型方法。该示例过程显示以下输出:

 

Is generic method definition: False
Is generic method: True

 

同样,以下示例对泛型类型定义进行操作:

 

Public Function GenericMethod(Of M)(ByVal item1 As M) As String
    Return String.Format("You passed in: {0}", item1)
End Function

 

通过调用 MakeGenericMethod 方法,传入 Integer 类型,然后该代码继续在运行时创建以下泛型方法(item1 代表您从调用代码传入的类型):

 

Public Function GenericMethod (ByVal item1 As Integer) As String
    Return String.Format("You passed in: {0}", item1)
End Function

 

Back to top

创建泛型类型实例和调用方法

假设您的目标之一是能够创建泛型类型的实例,并有可能调用构造泛型方法,那么,您将需要更多的技巧。为了创建一个类型的实例,无论在该类型的整个依赖关系链的哪个位置,均不能有类型占位符未得到满足的泛型类型定义或泛型方法定义。所有类型占位符均已指定的泛型类型称为封闭式泛型类型(同样的术语也适用于方法)。只能创建封闭式泛型类型的实例,同样,也只能调用封闭式泛型方法。

但是,如果泛型类型具有复杂的继承层次结构,该怎么办呢?如何确定类型继承关系中没有任何开放式类型呢?若要对此进行判定,您可以检索 Type.ContainsGenericParameters 属性,它可以明确指出您使用的是封闭式还是开放式泛型类型:如果该属性返回 True,则表明有一个开放式泛型类型,您不能创建该类型的实例。

TestForOpenOrClosedGeneric 过程演示了这一行为,其中使用了 TestClasses 文件中的两个类,如下所示:

 

Public Class BaseClass(Of T, U)
    Public Items As New Dictionary(Of T, U)
End Class

Public Class DerivedClass(Of V) 
    Inherits BaseClass(Of Integer, V)

End Class

 

TestForOpenOrClosedGeneric 过程调用图 3 中的 DisplayGenericTypeInfo 过程,显示了几个不同类型的相关信息。

对于给定的类型,DisplayGenericTypeInfo 过程首先显示类型名称、类型是否为泛型类型定义,以及是否为泛型类型。随后显示该类型是否包含泛型参数(如果该类型是泛型类型定义,它就会包含泛型参数,因而不能进行实例化。)图 3 中的代码然后会调用 GetGenericArguments 方法,检索所有泛型参数的列表,并在输出中显示每个泛型参数。最后,该代码使用 ContainsGenericParameters 属性指明该类型是开放式还是封闭式。(注意,可以将同一种逻辑应用于 MethodInfo 实例,以判断特定的 MethodInfo 表示开放式或是封闭式泛型方法。)

TestForOpenOrClosedGeneric 方法包含以下代码,测试作为泛型定义和作为构造类型的几个示例方法。看看您是否能预测每个测试的输出:

 

Dim baseType As Type = GetType(BaseClass(Of ,))
DisplayGenericTypeInfo(baseType)

Dim derivedType As Type = GetType(DerivedClass(Of ))
DisplayGenericTypeInfo(derivedType)
DisplayGenericTypeInfo(derivedType.BaseType)

Dim constructedType As Type = GetType(DerivedClass(Of String))
DisplayGenericTypeInfo(constructedType)

DisplayGenericTypeInfo(constructedType.BaseType)

 

CreateGenericTypeGivenGenericTypeDefinition 示例过程显示了如何在给定一个泛型类型定义的情况下创建一个封闭式泛型类型。您已经看到该技巧应用于方法定义:您调用 MethodInfo.MakeGenericMethod 方法,依据泛型方法定义构造泛型方法;与此类似,您可以调用 Type.MakeGenericType 方法,依据泛型类型定义构造泛型类型。正如您在图 4 中所见,示例代码在运行时创建一个构造类型,并与设计时创建的构造类型相比较。

为了调用一个给定其泛型方法定义的泛型方法,您必须调用 MethodInfo.MakeGenericMethod 方法,传递一个包含 Type 对象的数组,其中 Type 对象对应于每个方法的类型占位符。对于给定的新构造方法,您可以调用 MethodInfo.Invoke 方法,为该方法提供一个宿主对象实例和一个参数数组。当然,参数的类型必须正确地匹配您在构造该泛型方法实例时指定的占位符类型的类型。

图 5 中的 CallGenericMethodGivenGenericMethodDefinition 示例过程演示了这一技巧。此示例首先检索一个 MethodInfo 实例,该实例对应 GenericClass 中的 GenericMethod 方法。此方法接受单个类型占位符,只返回一个字符串,其中包含您在运行时提供的值:

 

Public Function GenericMethod(Of M)(ByVal item1 As M) As String
    Return String.Format("You passed in: {0}", item1)
End Function

 

以下代码构造一个特定的泛型类,为该类的类型占位符提供了一个类型:

 

Dim type1 As Type = GetType(GenericClass(Of Integer))
Dim mi As MethodInfo = type1.GetMethod("GenericMethod")
DisplayGenericMethodInfo(mi)

 

前面已经提到,如果一个泛型方法的继承关系中存在未满足的类型占位符(就是说,如果它不是封闭式泛型方法),则不能调用该方法。要验证这一点,您可以原封不动地运行一次该过程,然后修改该过程,以便为 type1 指定一个泛型类型定义。代码虽然能够编译,但会在运行时出错:

 

Dim type1 As Type = GetType(GenericClass(Of ))

 

示例代码会调用 DisplayGenericMethodInfo 过程,显示有关泛型方法及其每个泛型参数的信息。

该代码接下来创建一个 Type 实例的数组,然后调用 MethodInfo.MakeGenericMethod 方法创建一个构造泛型方法,如下所示:

 

Dim argTypes() As Type = {GetType(String)}
Dim miConstructed As MethodInfo = mi.MakeGenericMethod(argTypes)
DisplayGenericMethodInfo(miConstructed)

 

对于给定的构造泛型方法,该代码创建 GenericClass 的实例(您会在下一个演示中看到具体的操作方法),创建一个参数数组传递给该方法,并调用 MethodInfo.Invoke 来执行该构造泛型方法:

 

Dim host As New GenericClass(Of Integer)

' Supply the method parameters, and then invoke the method:
Dim args() As Object = {"test value"}
AddToResults("{0}", miConstructed.Invoke(host, args))

 

要查看具体运作,您可以创建同一个泛型方法定义的多个不同构造版本。该代码随后创建一个新的泛型方法定义,这一次指示它将传递一个整数(而不是字符串)。跟前面一样,该代码创建构造泛型方法,然后调用该方法:

 

argTypes(0) = GetType(Integer)
miConstructed = mi.MakeGenericMethod(argTypes)
DisplayGenericMethodInfo(miConstructed)

' Supply the method parameters, and then invoke the method:
args(0) = 13
AddToResults("{0}", miConstructed.Invoke(host, args))

 

CreateClosedGenericInstance 示例方法使用类似技巧,只是改为创建一个构造泛型类型而已。在前面的示例中,该代码在设计时创建构造类型。在此示例中,创建发生在运行时。

Back to top

使用类型参数

虽然您已看到检索有关泛型参数信息的代码,但有一些微妙之处值得深究。RetrieveTypeParameterInfo 示例过程使用几个不同的泛型类型和泛型类型定义,并显示各自的泛型参数信息。Type.GetGenericArguments 方法返回一个数组,如果当前类型为泛型类型定义,该数组就会包含泛型类型定义的类型参数,顺序为它们在类型定义中出现的顺序。如果当前类型为封闭式构造类型,该数组就会包含已分配给泛型类型参数的类型。如果当前类型为泛型类型定义,该数组就会包含类型参数。如果当前类型为开放式构造类型(就是说,并不是所有参数都已满足,此时 ContainsGenericParameters 属性将返回 True),该数组就会既包含类型又包含类型参数。可以使用 IsGenericParameter 属性对这二者进行区别。

RetrieveTypeParameterInfo 过程包含下列代码:

 

Dim type1 As Type = GetType(DerivedClass(Of String))
DisplayGenericParameterInfo(type1)

Dim type2 As Type = type1.GetGenericTypeDefinition
DisplayGenericParameterInfo(type2)

Dim type3 As Type = type2.BaseType
DisplayGenericParameterInfo(type3)

 

此代码显示的是一个构造泛型类型、其对应的泛型类型定义和该泛型类型定义的基类型(BaseClass 示例类)的参数信息。

在此示例中,DisplayGenericParameterInfo 做了大部分的工作,它包含图 6 中的代码。在显示类型名称之后,该代码还显示类型是否为泛型类型定义、是否为泛型类型以及是否包含泛型参数的指示信息。如果它是泛型类型,代码就会检索泛型参数的数组:

 

Dim args As Type() = t.GetGenericArguments()

 

然后,对于数组中的每个 Type 实例,代码将判断它是否有一个泛型参数(或构造类型的 Type)。如果有,就会显示 GenericParameterPosition 属性值;如果没有,则显示类型名称。(对于不是泛型参数的类型,检索其 GenericParameterPosition 属性会引发运行时异常。)

确定泛型参数的源

如果某代码向您提供了一个泛型参数,您可能需要确定它是泛型类型的参数还是泛型方法的参数。它可能来自一个您正在检查的类型,也可能来自一个封闭类型,或来自一个泛型方法。您如何确定它的来源?首先,检查类型的 DeclaringMethod 属性。如果该值不是 Nothing,它就会是 MethodInfo 实例,您就知道该类型参数来自一个泛型方法,并且 MethodInfo 的 IsGenericMethodDefinition 属性将返回 True。如果源不是一个泛型方法,DeclaringMethod 属性就会返回 Nothing,此时您可以检索 DeclaringType 属性。此类型始终为泛型类型定义。只有在类型的 IsGenericParameter 属性返回 True 时才能检索 DeclaringMethod 属性;否则会引发异常。

Back to top

泛型类型参数约束

最后,您可能需要调查泛型类型参数约束。这些约束对类型参数强制执行规则,确保泛型类型遵从应用程序要求。您可以添加约束,强制某个类型参数继承一个特定的类,实现一个或多个特定的接口,作为值类型,作为引用类型,并/或提供一个默认的构造函数。您可能想以编程方式确定应用于类型参数的约束(如果有),以便动态地构造满足约束条件的类型。

为了调查约束,您必须采取两套步骤。为了检索类型参数必须继承的一个类或者必须实现的一个或多个接口的相关信息,您需要调用 Type.GetGenericParameterConstraints 方法。此方法返回一个 Type 对象的数组,其中每个对象都表示一个类或接口。

要检索有关方差和其他特殊约束类型的信息,您必须使用两个单独的位掩码。对于方差约束,您检索类型的 GenericParameterAttributes 属性。然后,您必须将 GenericParameterAttributes.VarianceMask 位掩码应用于返回值。将结果与 GenericParameterAttributes.Covariant 或 Contravariant 枚举值相比较,以确定类型参数是否具有方差约束。

要检索有关特殊约束的信息,请应用 GenericParameterAttributes.SpecialConstraintMask 位掩码。这一次,将结果与 ReferenceTypeConstraint、NotNullableValueTypeConstraint 和 DefaultConstructorConstraint 枚举值相比较,以确定每个特殊约束的状态。ExamineGenericParameterAttributes 方法为您完成所有这些工作,它使用 IsBitSet 辅助过程来确定是否设置了特定的位:

 

Private Function IsBitSet( _
        ByVal value As Integer, ByVal bitValue As Integer) As Boolean
    Return (value And bitValue) = bitValue
End Function

 

ExamineGenericParameterAttributes 过程包含图 7 中的代码,该代码完全遵照上面的步骤。

DisplayConstraintInfo 示例过程包含图 8 中的代码,显示所提供的类型的参数约束信息。

如前面所述,此过程会遍历所有参数;对于每个参数,首先调用 ExamineGenericParameterAttributes 过程(处理方差和其他特殊约束),然后遍历所有通过调用 GetGenericParameterConstraints 方法而得到的类型。

ExamineConstraints 示例过程如下所示:

 

DisplayConstraintInto(GetType(TestClass1(Of )))
DisplayConstraintInto(GetType(TestClass2(Of )))
DisplayConstraintInto(GetType(TestClass3(Of )))

 

该代码为三个不同的类调查 DisplayConstraintInfo 过程的行为,这三个类均具有不同的参数约束。这三个示例类如图 9 所示。

现在轮到您了

到目前为止,您已了解 .NET Framework 所提供的有关反射和泛型的各种方法和属性。现在,您要做的全部工作就是下载示例应用程序,演练一遍所有的代码和输出,阅读相关的文档主题,然后就能泰然自若地处理任何与反射和泛型相关的问题了。

Back to top

请将您想向 Ken 询问的问题或提出的意见发送至 basics@microsoft.com.
Download Image NEW: Explore the sample code online! - or - 代码下载位置: AdvancedBasics2008_01.exe (181KB)

Ken Getz 是 MCW Technologies 的高级顾问和 AppDev (www.appdev.com) 的课件作者。他是“ASP .NET Developers Jumpstart”(ASP .NET 开发人员入门)(Addison-Wesley, 2002)、“Access Developer's Handbook”(Access 开发人员手册)(Sybex, 2002) 和“VBA Developer's Handbook, 2nd Edition”(VBA 开发人员手册,第 2 版)(Sybex, 2001) 的合著者。您可以通过 keng@mcwtech.com 与他联系。

Subscribe  摘自 January 2008 期刊 MSDN Magazine.
Back to topBack to top QJ: 080110

© 2007 Microsoft Corporation 与 CMP Media, LLC.保留所有权利;不得对全文或部分内容进行复制.
 
MSDN 杂志的相关文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值