反思泛型






4.77/5 (20投票s)
如何使用反射来访问、使用和解释泛型类和方法
引言
你是否曾经不得不这样做?
string typeName = "MyDataType";
Type type = Type.GetType( typeName );
object o = someGenericClass<typeof(type)>();
如果你这样做过,那么你可能也发现这无法编译。好消息是,C# 中可以实现这个功能,但它并不明显。本文旨在澄清围绕反射和泛型
类型的迷雾。我们将展示如何使用泛型
类来实现这一点(并且,额外赠送,还将展示泛型
方法的等效功能)。
背景
你为什么会需要这样做呢?我在创建一个应用程序框架时遇到了这个问题,该框架利用了动态加载的、具有明确定义接口的 .NET 程序集。其中一些接口是泛型
的,而另一些则包含泛型
方法。
问题出现在实现框架时,当时数据类型在编译时是未知的,而是通过Type
对象动态发现的。以任何明显的方式,使用动态加载的数据类型来实例化动态加载的泛型
类型是不可能的。
经过大量的互联网搜索和 MSDN 的深入研究,我终于发现了使用泛型反射的秘诀。
类型和泛型
这一切的关键在于理解可以与Generic
类关联的两种不同的Type
实例。当你定义一个Generic
类时,你并没有定义一个或多个成员类型,直到它在代码中的某个地方被使用。这被称为“开放”泛型。当你在代码中声明对这些Generic
类之一的引用并提供实际的类型时,你就“完成”了类的定义。这被称为“封闭”泛型。
在我们的所有示例中,我们将使用以下Generic
类定义
namespace ReflectGenerics
{
public class GenericClass<T>
{
public GenericClass( T t )
{
_t = t;
Console.WriteLine( "GenericClass<{0}>( {1} ) created",
typeof( T ).FullName, _t.ToString() );
}
private T _t = default( T );
public T GetValue()
{
Console.WriteLine( "GetValue() invoked, returning {0}", _t.ToString() );
return _t;
}
public static U StaticGetValue<U>( U u )
{
Console.WriteLine( "StaticGetValue<{0}>( {1} ) invoked",
typeof( U ).FullName, u.ToString() );
return u;
}
}
}
让我们看一些代码示例
// create an instance of a generic class
GenericClass<int> t = new GenericClass<int>( 1 );
// get the type of the generic class definition using just the class name
string typeName = "ReflectGenerics.GenericClass";
Type openGenericClass = Type.GetType( typeName );
// get the type of the generic class with the generic parameter defined
Type closedGenericClass = typeof( GenericClass<int> );
如果我们通过调试器单步执行这段代码并检查Type
对象,我们可以看到openGenericClass
和closedGenericClass
之间的一些差异。Type
类有一些属性可以帮助我们确定Generic
类处于什么状态。相关的属性是:IsGenericType
和IsGenericTypeDefinition
。每个属性都返回bool
。
如果我们检查openGenericClass
,我们会发现IsGenericType
是true
,而IsGenericTypeDefinition
是true
。如果我们检查closedGenericClass
,我们会发现IsGenericType
是true
,而IsGenericTypeDefinition
是false
。我们可以从中得知,如果一个类是Generic
类,那么它关联的Type
对象将始终返回true
给IsGenericType
。但是,如果Type
代表一个Generic
类,并且它的类型参数未定义,那么IsGenericTypeDefinition
也将返回true
。
要仅从Type
定义创建对象实例,我们可以使用Activator
类。但是,要实例化一个Generic
类型,我们需要一个封闭的Type
(即,所有泛型类型参数都已定义)。好的!这很好。但是,如果我们有一个用于泛型类型参数的Type
对象和一个开放的泛型Type
怎么办?我们如何将它们转换为一个封闭的泛型Type
?
Type
类有一个名为MakeGenericType
的辅助方法,它可以将开放类型转换为封闭类型。
// get the type of the generic class from the class definition type
Type dynamicClosedGenericClass = openGenericClass.MakeGenericType( typeof( int ) );
现在我们已经获取了一个开放类型并为其添加了必要的泛型类型参数,我们可以使用这个封闭类型来实例化一个对象。
object o = Activator.CreateInstance( dynamicClosedGenericClass, 1 );
object rv = dynamicClosedGenericClass.InvokeMember
( "GetValue", BindingFlags.InvokeMethod, null, o, new object[ 0 ] );
Console.WriteLine( "GetValue() returned {0}", rv.ToString() );
请注意,这段代码在功能上等同于
GenericClass<int> t = new GenericClass<int>( 1 );
Console.WriteLine( "GetValue() returned {0}", t.GetValue() );
泛型方法
我们唯一需要弄清楚的就是泛型方法。我们的示例generic
类有一个static generic
方法,用于作为示例。我们学到的关于类开放generic
类型和封闭generic
类型的所有知识也true
地适用于方法,只是我们需要使用MethodInfo
类而不是Type
类。
// invoke the static template method directly
GenericClass<int>.StaticGetValue( 3 );
// get the open generic method type
MethodInfo openGenericMethod =
typeof( GenericClass<> ).GetMethod( "StaticGetValue" );
// get the close generic method type, by supplying the generic parameter type
MethodInfo closedGenericMethod =
openGenericMethod.MakeGenericMethod( typeof( int ) );
object o2 = closedGenericMethod.Invoke( null, new object[] { 4 } );
Console.WriteLine( "o2 = {0}", o2.ToString() );
MethodInfo
具有IsGenericMethod
和IsGenericMethodDefinition
属性。这些属性与我们正在检查的Type
类中的两个属性完全类似。MethodInfo
同样可以是“开放”或“封闭”的,如果它引用了一个泛型方法。最后,将开放MethodInfo
转换为封闭MethodInfo
是通过MakeGenericMethod
方法完成的。
摘要
因此,我们已经学会了如何获取一个Type
实例并检查它是否是generic
的,以及它是开放的还是封闭的。如果是开放的,我们学会了如何使用MakeGenericType
方法将其关闭,以便可以使用Activator
进行实例化。我们还学会了如何使用MethodInfo
类上的MakeGenericMethod
方法将开放MethodInfo
关闭,从而对泛型方法执行等效的操作。
历史
- 2007年12月12日:初始版本