Saturday, August 14, 2004

 

Type, RuntimeType and RuntimeTypeHandle

1.
作者:flier
出处:http://www.blogcn.com/user8/flier_lu/main.asp?id=1635813

Vijay http://dotnetjunkies.com/WebLog/unknownreference/ 在其 BLog 上的一篇文章 A .NET Riddle http://dotnetjunkies.com/WebLog/unknownreference/archive/2004/02/12/7081.aspx 中提出了一个有趣的问题:

以下为引用:

What is the only Type in .NET which has only a non-public constructor and the method body of which throws up a NotImplemented Exception. If so how is the type instance created then ?

答案就是 System.RuntimeType 类型。Vijay 在接下来的一篇文章 .NET Riddle Redux - The Answer http://dotnetjunkies.com/WebLog/unknownreference/archive/2004/02/14/7233.aspx 中解释了为什么如此,并且介绍了 Rotor 中 RuntimeType 类型获得的方法。
Rotor 中 System.RuntimeType 类型的构造函数(bcl\system\RuntimeType.cs:59)代码如下:

以下为引用:

internal sealed class RuntimeType : Type, ISerializable, ICloneable
{
// Prevent from begin created
internal RuntimeType()
{
throw new NotSupportedException(Environment.GetResourceString(ResId.NotSupported_Constructor));
}
}

正如前面那个谜语中所说,有一个内部构造函数但函数体直接抛出异常,因此不能直接构造。唯一能够获得此类型实例的方式是通过Object.GetType()等函数,由 CLR 创建。

下面我们简单介绍一下,Type、RuntimeType 和 RuntimeTypeHandle 三个类型之间的关系。

简单的说:Type 是一个表示类型的抽象类;RuntimeType 是 Type 针对载入类型信息的具体实现;RuntimeTypeHandle 则是类型唯一的抽象句柄。

以下为引用:

namespace System
{
public struct RuntimeTypeHandle : ISerializable
{
private IntPtr m_ptr;

//...
}

public abstract class Type : MemberInfo, IReflect
{
//...

public abstract RuntimeTypeHandle TypeHandle
{
get;
}

public static RuntimeTypeHandle GetTypeHandle(Object o)
{
return RuntimeType.InternalGetTypeHandleFromObject(o);
}

public static Type GetTypeFromHandle(RuntimeTypeHandle handle)
{
return RuntimeType.GetTypeFromHandleImpl(handle);
}

//...
}

internal sealed class RuntimeType : Type, ISerializable, ICloneable
{
//...

public override RuntimeTypeHandle TypeHandle
{ get { return InternalGetClassHandle(); } }

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern RuntimeTypeHandle InternalGetClassHandle();

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal extern static RuntimeTypeHandle InternalGetTypeHandleFromObject(Object o);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern Type GetTypeFromHandleImpl(RuntimeTypeHandle handle);

//...
}
}

Type 使用最为广泛,提供了完整的类型信息获取的接口,可以通过 C# 的 typeof 关键字或者 Object.GetType() 函数从类型和对象直接获取。但 Type 只是一个抽象类,需要通过子类实现其部分功能。除了常见的 RuntimeType 子类外,还有 System.Reflection 名字空间下的 TypeDelegator 和 System.Reflection.Emit 名字空间下的 EnumBuilder、TypeBuilder 和 SymbolType 从其直接继承。
RuntimeType 是 System 名字空间的内部类,用于实现对普通类型的运行时信息提供代码。Type中很多抽象函数如 Type.GetMethodImpl 都是在 RuntimeType 中实现的。与 TypeBuilder 等不同,RuntimeType 是类型的运行时表现。
RuntimeTypeHandle 是运行时类型的抽象句柄,结构内部实际上是一个 Unamanged 指针。可以使用 Type.GetTypeFromHandle() 函数、Type.TypeHandle 属性和 Type.GetTypeHandle() 函数,完成在类型句柄、类型和对象之间的双向转换。例如:

以下为引用:

MyClass1 cls = new MyClass1();

Debug.Assert(cls.GetType().TypeHandle.Equals(Type.GetTypeHandle(cls)));
Debug.Assert(Type.GetTypeFromHandle(cls.GetType().TypeHandle) == typeof(MyClass1));

从 Rotor 的实现代码上来看,Type 类型的实际构造基本上都是由 RuntimeType 类型通过 COMClass 类(vm\ComClass.cpp:260)完成的;而 RuntimeTypeHandle 则是类型方法表 MethodTable 类(vm\Class.h:293)的指针的简单包装。
获取 RuntimeTypeHandle 的函数 Type.GetTypeHandle() 实际上调用的是 RuntimeType.InternalGetTypeHandleFromObject 函数(bcl\system\RuntimeType.cs:592);而此函数的内部实现被绑定(vm\ECall.cpp:515)到 COMClass::GetTHFromObject 函数(vm\ComClass.cpp:255)上;最终只是简单地返回对象的 MethodTable 指针。伪代码如下

以下为引用:

void * COMClass::GetTHFromObject(Object* obj)
{
if(obj==NULL)
FCThrowArgumentNull(L"obj");

return obj->GetMethodTable();
}

因此在 Unmanaged C++ 中实际上可以直接通过 GetTypeHandle 返回的 RuntimeTypeHandle.Data 转换为地址指针,访问 MethodTable 类的数据。例如 MethodTable 第二个 DWORD 中保存着此对象的基本大小,可以在 VS.NET 调试环境的内存窗口中查看到。

直接从类型获取 RuntimeTypeHandle 的 Type.InternalGetClassHandle 函数(bcl\system\RuntimeType.cs:589),内部实现被绑定(vm\ECall.cpp:528)到 COMClass::GetClassHandle 函数(vm\ComClass.cs:1434)上,直接从类型的内部包装类 ReflectClass 类(vm\ReflectorWrap.h:212)中获取初始化时提供的类型句柄(MethodTable指针)。伪代码如下

以下为引用:

void * COMClass::GetClassHandle(ReflectClassBaseObject* refThisUNSAFE)
{
REFLECTCLASSBASEREF refThis = (REFLECTCLASSBASEREF) refThisUNSAFE;

ReflectClass* pRC = (ReflectClass*) refThis->GetData();

TypeHandle ret = pRC->GetTypeHandle();

return ret.AsPtr();
}

从 RuntimeTypeHandle 反向查询 Type 的 Type.GetTypeFromHandle 函数,实现上调用 RuntimeType.GetTypeFromHandleImpl 函数(bcl\system\RuntimeType.cs:596),并被绑定(vm\ECall.cpp:529)到 COMClass::GetClassFromHandle 函数(vm\ComClass.cpp:1451)上。
GetClassFromHandle 函数首先试图从 RuntimeTypeHandle 实例指向的方法表中,调用 MethodTable::GetExistingExposedClassObject 函数(vm\Class.h:488)获取已经建立的类型信息包装类;如果第一次使用此类型,则调用 TypeHandle::CreateClassObj 函数(vm\Class.cpp:12486)创建类型信息包装类实例。伪代码如下:

以下为引用:

Object * COMClass::GetClassFromHandle(LPVOID handle)
{
if(handle == 0)
FCThrowArgumentEx(kArgumentException, NULL, L"InvalidOperation_HandleIsNotInitialized");

TypeHandle typeHnd(handle);

if(!typeHnd.IsTypeDesc())
{
MethodTable *pMT = typeHnd.GetMethodTable();

if (pMT)
{
OBJECTREF o = pMT->GetExistingExposedClassObject(); // 获取已经建立的类型信息包装类
if (o != NULL)
{
return (OBJECTREFToObject(o));
}
}
}

return typeHnd.CreateClassObj(); // 创建类型信息包装类实例
}

2.
在.NET运行时了解类型信息

原文出处:MSDN
翻译:Paul_Ni
出处:http://www.csdn.net/develop/Read_Article.asp?Id=13335
http://www.csdn.net/develop/Read_Article.asp?Id=13336
http://www.csdn.net/develop/Read_Article.asp?Id=13337

通过反射命名空间中的类以及 System.Type,您可以获取有关已加载的程序集和在其中定义的类型(如类、接口和值类型)的信息。您也可以使用反射在运行时创建类型实例,然后调用和访问这些实例。


反射概述


公共语言运行库加载器管理应用程序域。这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。



程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。反射通常具有以下用途:





使用 Assembly 定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例。

使用 Module 了解如下的类似信息:包含模块的程序集以及模块中的类等。您还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。

使用 ConstructorInfo 了解如下的类似信息:构造函数的名称、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。使用 Type 对象的 GetConstructors 或 GetConstructor 方法来调用特定的构造函数。

使用 MethodInfo 来了解如下的类似信息:方法的名称、返回类型、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。使用 Type 对象的 GetMethods 或 GetMethod 方法来调用特定的方法。

使用 FieldInfo 来了解如下的类似信息:字段的名称、访问修饰符(如 public 或 private)和实现详细信息(如 static)等;并获取或设置字段值。

使用 EventInfo 来了解如下的类似信息:事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等;并添加或移除事件处理程序。

使用 PropertyInfo 来了解如下的类似信息:属性的名称、数据类型、声明类型、反射类型和只读或可写状态等;并获取或设置属性值。

使用 ParameterInfo 来了解如下的类似信息:参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。


System.Reflection.Emit 命名空间的类提供了一种特殊形式的反射,使您能够在运行时构造类型。



反射也可用于创建称作类型浏览器的应用程序,它使用户能够选择类型,然后查看有关选定类型的信息。



反射还有其他一些用途。JScript 等语言编译器使用反射来构造符号表。System.Runtime.Serialization 命名空间中的类使用反射来访问数据并确定要持久保存的字段。System.Runtime.Remoting 命名空间中的类通过序列化来间接地使用反射。



查看类型信息


System.Type 类对于反射起着核心的作用。当反射请求加载的类型时,公共语言运行库将为它创建一个 Type 对象。您可以使用 Type 对象的方法、字段、属性和嵌套类来查找有关该类型的所有信息。



在使用 Assembly.GetType 或 Assembly.GetTypes 时传入所需类型的名称,可以从尚未加载的程序集中获取 Type 对象。使用 Type.GetType 可从已加载的程序集中获取 Type 对象。使用 Module.GetType 和 Module.GetTypes 可获取模块 Type 对象。



以下代码示例显示在获取程序集的 Assembly 对象和模块时所必需的语法。

[C#]
// Get the mscorlib assembly in which the object is defined.
Assembly a = typeof(Object).Module.Assembly;


以下示例代码说明如何从已加载的程序集中获取 Type 对象。

[C#]
// Load an assembly using its file name.
Assembly a = Assembly.LoadFrom ("MyExe.exe");
// Get the type names from the assembly.
Type [] types2 = a.GetTypes ();
foreach (Type t in types2)
{
Console.WriteLine (t.FullName);
}


获取 Type 对象之后,可以通过多种方法来了解有关该类型成员的信息。例如,通过调用 Type.GetMembers 方法(该方法将获取对当前类型的每个成员进行描述的一组 MemberInfo 对象),您可以获取有关该类型的所有成员的信息。



您也可以在 Type 类上使用方法,以检索有关按名称指定的一个或多个构造函数、方法、事件、字段或属性的信息。例如,Type.GetConstructor 封装当前类的特定构造函数。



如果具有 Type 对象,则可以使用 Type.Module 属性来获取一个封装该类型所在模块的对象。使用 Module.Assembly 属性可查找封装模块所在程序集的对象。使用 Type.Assembly 属性可直接获取封装类型的程序集。



System.Type 和 ConstructorInfo


以下代码示例显示如何列出一个类(此示例中为 String 类)的构造函数。

[C#]
// This program lists all the public constructors
// of the System.String class.
using System;
using System.Reflection;
class ListMembers {
public static void Main(String[] args) {
Type t = typeof(System.String);
Console.WriteLine ("Listing all the public constructors of the {0} type", t);
// Constructors
ConstructorInfo[] ci = t.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
Console.WriteLine ("//Constructors");
PrintMembers (ci);
}
public static void PrintMembers(MemberInfo [] ms) {
foreach (MemberInfo m in ms) {
Console.WriteLine ("{0}{1}", " ", m);
}
Console.WriteLine();
}
}


MemberInfo、MethodInfo、FieldInfo 和 PropertyInfo


使用 MemberInfo、MethodInfo、FieldInfo 或 PropertyInfo 对象可获取有关类型的方法、属性、事件、字段的信息。



以下代码示例使用 MemberInfo 来列出 System.IO.File 类中的成员数量并使用 System.Type.IsPublic 属性来确定该类的可见性。

[C#]
using System;
using System.IO;
using System.Reflection;

class Mymemberinfo
{
public static void Main(string[] args)
{
Console.WriteLine ("\nReflection.MemberInfo");
// Get the Type and MemberInfo.
Type MyType =Type.GetType("System.IO.File");
MemberInfo[] Mymemberinfoarray = MyType.GetMembers();
// Get and display the DeclaringType method.
Console.WriteLine("\nThere are {0} members in {1}.",
Mymemberinfoarray.Length, MyType.FullName);
Console.WriteLine("{0}.", MyType.FullName);
if (MyType.IsPublic)
{
Console.WriteLine("{0} is public.", MyType.FullName);
}
}
}


以下代码示例调查指定成员的类型。它对 MemberInfo 类的一个成员执行反射,然后列出其类型。

[C#]
// This code displays information about the GetValue method of FieldInfo.
using System;
using System.Reflection;
class MyMethodInfo {
public static int Main() {
Console.WriteLine("Reflection.MethodInfo");
// Get and display the Type.
Type MyType = Type.GetType("System.Reflection.FieldInfo");
// Specify the member for which you want type information here.
MethodInfo Mymethodinfo = MyType.GetMethod("GetValue");
Console.WriteLine(MyType.FullName + "." + Mymethodinfo.Name);
// Get and display the MemberType property.
MemberTypes Mymembertypes = Mymethodinfo.MemberType;
if (MemberTypes.Constructor == Mymembertypes) {
Console.WriteLine("MemberType is of type All");
}
else if (MemberTypes.Custom == Mymembertypes) {
Console.WriteLine("MemberType is of type Custom");
}
else if (MemberTypes.Event == Mymembertypes) {
Console.WriteLine("MemberType is of type Event");
}
else if (MemberTypes.Field == Mymembertypes) {
Console.WriteLine("MemberType is of type Field");
}
else if (MemberTypes.Method == Mymembertypes) {
Console.WriteLine("MemberType is of type Method");
}
else if (MemberTypes.Property == Mymembertypes) {
Console.WriteLine("MemberType is of type Property");
}
else if (MemberTypes.TypeInfo == Mymembertypes) {
Console.WriteLine("MemberType is of type TypeInfo");
}
return 0;
}
}


以下示例代码使用所有的反射 *Info 类以及 BindingFlags 来列出指定类的所有成员(构造函数、字段、属性、事件和方法),并将这些成员划分为静态和实例类别。

[C#]
// This program lists all the members of the
// System.IO.BufferedStream class.
using System;
using System.IO;
using System.Reflection;

class ListMembers {
public static void Main(String[] args) {
// Specify the class here.
Type t = typeof (System.IO.BufferedStream);
Console.WriteLine ("Listing all the members (public and non public) of the {0} type", t);

// Static Fields are listed first.
FieldInfo [] fi = t.GetFields (BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public);
Console.WriteLine ("// Static Fields");
PrintMembers (fi);

// Static Properties
PropertyInfo [] pi = t.GetProperties (BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public);
Console.WriteLine ("// Static Properties");
PrintMembers (pi);

// Static Events
EventInfo [] ei = t.GetEvents (BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public);
Console.WriteLine ("// Static Events");
PrintMembers (ei);

// Static Methods
MethodInfo [] mi = t.GetMethods (BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public);
Console.WriteLine ("// Static Methods");
PrintMembers (mi);

// Constructors
ConstructorInfo [] ci = t.GetConstructors (BindingFlags.Instance |
BindingFlags.NonPublic | BindingFlags.Public);
Console.WriteLine ("// Constructors");
PrintMembers (ci);

// Instance Fields
fi = t.GetFields (BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public);
Console.WriteLine ("// Instance Fields");
PrintMembers (fi);

// Instance Properites
pi = t.GetProperties (BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public);
Console.WriteLine ("// Instance Properties");
PrintMembers (pi);

// Instance Events
ei = t.GetEvents (BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public);
Console.WriteLine ("// Instance Events");
PrintMembers (ei);

// Instance Methods
mi = t.GetMethods (BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public);
Console.WriteLine ("// Instance Methods");
PrintMembers (mi);

Console.WriteLine ("\r\nPress Return to exit.");
Console.Read();
}

public static void PrintMembers (MemberInfo [] ms) {
foreach (MemberInfo m in ms) {
Console.WriteLine ("{0}{1}", " ", m);
}
Console.WriteLine();
}
}

反射类所使用的设计模式
System.Reflection 类中最常用的方法都使用统一的模式。Module、Type 和 MemberInfo 类的成员使用下表中所示的设计模式。

成员签名
说明

MyInstance[] FindXxx(filter, filterCriteria)
查找并返回经过筛选的类型列表,或者在当前类型没有实现任何匹配筛选器的类型的情况下返回空数组。

例如:Type.FindInterfaces

MyInstance GetXxx()
返回由 唯一指定的类型。如果不存在这样的类型,成员将返回 null(在 Microsoft Visual Basic .NET 中为 Nothing)。请注意, 唯一地指定一个实例。

例如:Type.GetInterface

MyInstance[] GetXxxs()
返回所有公共类型。如果不存在公共类型,成员将返回空数组。

例如:Type.GetFields

MyInstance[] GetXxxs()
返回由 指定的所有类型。如果不存在这样的类型,成员将返回空数组。请注意, 并不一定指定唯一的实例。


另一个常用的设计模式是使用委托。它们通常在反射中用来支持对返回对象数组的方法的结果集进行筛选。

反射的安全注意事项
如果提供对非公共信息的访问,将带来安全风险。如果允许代码了解某类型的非公共信息,那么该代码就有可能会访问您需要保密的代码、数据和其他信息。因此,.NET 框架安全性强制实施了一些规则,以确定可以使用哪种程度的反射来了解类型信息和访问类型。根据所执行的操作,可能会需要用于序列化的 ReflectionPermission 或 SecurityPermission。

在未经许可的情况下,所有代码都可以使用反射来执行以下任务:

获取有关公共类型及其公共成员的信息。
了解代码所在的模块和程序集。
枚举公共类型。
枚举与使用反射的代码位于同一程序集中的非公共类型。
枚举程序集和模块。
调用公共成员。
对调用代码基类的族访问成员进行调用。
对调用代码程序集的程序集访问成员进行调用。
若要了解有关非公共成员的信息,调用方必须具有 ReflectionPermission,此权限表示可以获取类型信息。如果不具有此权限,代码将无法使用反射通过 Type、Assembly 和 Module 上的 Get 方法来获取有关非公共成员(即使属于它自己的类)的信息。

要使用反射来调用无法按照通用类型系统可访问性规则访问的方法或访问这样的字段,必须向代码赋予成员访问的 ReflectionPermission。

注意 建议使安全策略拒绝向源自 Internet 的代码授予 ReflectionPermission。

序列化的 SecurityPermission 提供了获取和设置可序列化类型的任何非瞬态数据字段(即不仅存在于内存中的成员)的能力,而不论是否可以访问这些字段。此权限使代码能够了解并更改实例的私有状态。(除了授予正确的权限之外,还必须在元数据中将类型标记为可序列化。)

链接请求检查
如果方法或委托对某一 P 权限具有 LinkDemand,运行库将对该方法或委托的调用方执行链接请求检查,以验证已经向调用方授予 P 权限。在了解类型信息和进行调用时,都会执行此链接请求检查。

应避免编写采用 MethodInfo 参数的公共 API,尤其是对于高度信任的代码。否则,调用权限可能会很容易被恶意代码绕过。例如,设想在高度信任的代码中有一个采用 MethodInfo 参数的公共 API。假设此公共 API 对所提供的参数间接地调用 MethodInfo.Invoke。如果公共 API 没有执行必要的权限检查,由于安全性系统断定调用方受到高度信任,对 Invoke 方法的调用将始终会成功。即使恶意代码无权直接调用该方法,它仍然能够通过调用公共 API 来间接地调用该方法。

动态加载和使用类型
反射提供了由语言编译器(例如 Microsoft Visual Basic .NET 和 JScript)用来实现隐式晚期绑定的基础结构。绑定是查找与唯一指定的类型相对应的声明(即实现)的过程。由于此过程在运行时而不是在编译时发生,所以称作晚期绑定。Visual Basic .NET 允许您在代码中使用隐式的晚期绑定;Visual Basic 编译器将调用一个帮助器方法,该方法使用反射来获取对象类型。传递给帮助器方法的参数有助于在运行时调用正确的方法。这些参数包括对其调用方法的实例(对象)、被调用方法的名称(字符串)和传递给被调用方法的参数(对象数组)。

在以下代码示例中,Visual Basic 编译器使用反射隐式地对其类型在编译时未知的对象调用方法。HelloWorld 类具有一个 PrintHello 方法,它输出与传递给 PrintHello 方法的某些文本串联的“Hello World”。在该示例中调用的 PrintHello 方法实际上是 Type.InvokeMember;Visual Basic 代码允许按照对象 (helloObj) 的类型在编译时已知(早期绑定)而不是在运行时已知(晚期绑定)的方式来调用 PrintHello 方法。

自定义绑定
除了由编译器隐式地用来进行晚期绑定之外,反射还可以在代码中显式地用来完成晚期绑定。

公共语言运行库支持多种编程语言,但这些语言的绑定规则各不相同。在早期绑定的情况下,代码生成器可以完全控制此绑定。但是,当通过反射进行晚期绑定时,必须用自定义绑定来控制绑定。Binder 类提供了对成员选择和调用的自定义控制。

利用自定义绑定,您可以在运行时加载程序集,获取有关该程序集中类型的信息,然后对该类型调用方法或访问该类型的字段或属性。如果您在编译时(例如当对象类型依赖于用户输入时)不知道对象的类型,就可以使用这种方法。以下代码示例显示在 HelloWorld.dll 程序集中使用反射动态调用的方法(首先在 Visual Basic .NET 中,然后在 C# 中)。

[C#]
// This class is deployed as an assembly consisting of one DLL,
// called HelloWorld.dll.
using System;
public class HelloWorld {
// Constant Hello World string.
private const String m_helloWorld = "Hello World";
// Default public constructor.
public HelloWorld() {
}
// Print "Hello World" plus the passed text.
public void PrintHello(String txt) {
// Output to the Console.
Console.WriteLine(m_helloWorld + " " + txt);
}
}

// Illustrates reflection's late binding functionality.
// Calls the PrintHello method on a dynamically loaded
// and created instance of the HelloWorld class.
using System;
using System.Reflection;
public class CSharpLateHello {
public static void Main() {
// Load the assembly to use.
Assembly assem = Assembly.Load("HelloWorld");
// Get the type to use from the assembly.
Type helloType = assem.GetType("HelloWorld");
// Get the method to call from the type.
MethodInfo printMethod = helloType.GetMethod("PrintHello");
// Create an instance of the HelloWorld class.
Object obj = Activator.CreateInstance(helloType);
// Create the args array.
Object[] args = new Object[1];
// Set the arguments.
args[0] = "From CSharp Late Bound";
// Invoke the PrintHello method.
printMethod.Invoke(obj, args);
}
}
InvokeMember 和 CreateInstance
使用 Type.InvokeMember 可调用类型的成员。各个类(如 System.Activator 和 System.Reflection.Assembly)的 CreateInstance 方法是特殊形式的 InvokeMember,它们可新建特定类型的实例。Binder 类用于在这些方法中进行重载决策和参数强制。

以下代码示例显示参数强制(类型强制)和成员选择三种可能的组合。在第 1 种情况中,不需要任何参数强制或成员选择。在第 2 种情况中,只需要成员选择。在第 3 种情况中,只需要参数强制。

[C#]
public class CustomBinderDriver
{
public static void Main (string[] arguments)
{
Type t = typeof (CustomBinderDriver);
CustomBinder binder = new CustomBinder();
BindingFlags flags = BindingFlags.InvokeMethod|BindingFlags.Instance|
BindingFlags.Public|BindingFlags.Static;

//Case 1. Neither argument coercion nor member selection is needed.
args = new Object[] {};
t.InvokeMember ("PrintBob", flags, binder, null, args);

//Case 2. Only member selection is needed.
args = new Object[] {42};
t.InvokeMember ("PrintValue", flags, binder, null, args);

//Case 3. Only argument coercion is needed.
args = new Object[] {"5.5"};
t.InvokeMember ("PrintNumber", flags, binder, null, args);
}

public static void PrintBob ()
{
Console.WriteLine ("PrintBob");
}

public static void PrintValue (long value)
{
Console.WriteLine ("PrintValue ({0})", value);
}
public static void PrintValue (String value)
{
Console.WriteLine ("PrintValue\"{0}\")", value);
}

public static void PrintNumber (double value)
{
Console.WriteLine ("PrintNumber ({0})", value);
}
}
当多个成员具有相同的名称时,将需要重载决策。Binder.BindToMethod 和 Binder.BindToField 方法用于解析与单个成员的绑定。Binder.BindToMethod 还通过 get 和 set 属性访问器提供了属性解析。

BindToMethod 返回要调用的 MethodBase,如果无法进行这样的调用,则返回 null。虽然 MethodBase 返回值通常是 match 参数中所包含的值之一,但它并不必如此。

当存在 ByRef 参数时,调用方可能需要取回这些参数。因此,如果 BindToMethod 已经操作参数数组,Binder 会允许将参数数组映射回它的初始形式。为了实现这一目的,必须向调用方保证参数的顺序不会改变。当按名称传递参数时,联编程序将重新排列参数数组,这就是调用方所见的参数。

可用成员集包括在类型和任何基类型中定义的成员。如果指定 BindingFlags.NonPublic,将返回该成员集中具有任何可访问性的成员。如果未指定 BindingFlags.NonPublic,联编程序就必须强制可访问性规则。当指定 Public 或 NonPublic 绑定标志后,还必须指定 Instance 或 Static 绑定标志,否则不会返回任何成员。

如果只有一个成员具有给定名称,则不必进行回调,而在该方法上进行绑定。代码示例的第 1 种情况说明了这一点:只有一个 PrintBob 方法可用,因此不需要进行回调。

如果可用集中有多个成员,所有这些方法都将传递给 BindToMethod,它将选择正确的方法并将其返回。在代码示例的第 2 种情况下,有两个名为 PrintValue 的方法。对 BindToMethod 的调用将选择正确的方法。

ChangeType 执行参数强制(类型强制),以便将实参转换为选定方法的形参的类型。即使类型精确匹配,仍会为每个参数调用 ChangeType。

在代码示例的第 3 种情况下,类型为 String 值为“5.5”的实参传递给 Double 类型的形参的方法。要使调用成功,必须将字符串值“5.5”转换为 double 值。ChangeType 会执行此转换。

ChangeType 仅执行无损或扩大转换,如下表所示。

源类型
目标类型

任何类型
它的基类型

任何类型
它所实现的接口

Char
UInt16、UInt32、Int32、UInt64、Int64、Single、Double

Byte
Char、UInt16、Int16、UInt32、Int32、UInt64、Int64、Single、Double

SByte
Int16、Int32、Int64、Single、Double

UInt16
UInt32、Int32、UInt64、Int64、Single、Double

Int16
Int32、Int64、Single、Double

UInt32
UInt64、Int64、Single、Double

Int32
Int64、Single、Double

UInt64
Single、Double

Int64
Single、Double

Single
Double

非引用类型
引用类型


Type 类具有 Get 方法,这些方法使用 Binder 类型的参数来解析对特定成员的引用。Type.GetConstructor、Type.GetMethod 和 Type.GetProperty 通过提供成员的签名信息来搜索当前类型的特定成员。Binder.SelectMethod 和 Binder.SelectProperty 则被回调来选择相应方法的给定签名信息。

访问默认成员
任何类型都可以具有默认成员,即在未给定任何成员名称时调用的成员。以下代码示例调用 Class1 的默认成员,并将它返回的值赋给 i。

默认成员用 System.Reflection.DefaultMemberAttribute 进行标记。以下代码示例显示如何通过检索默认成员的自定义属性来检索默认成员。

[C#]
Type t = typeof(DefaultMemberAttribute);
DefaultMemberAttribute defMem = (DefaultMemberAttribute)Attribute.GetCustomAttribute(Assembly.GetAssembly(t), t);
MemberInfo[] memInfo = t.GetMember(defMem.MemberName);
使用 Type.GetDefaultMembers 可能会简单一些,并且会生成完全相同的结果。但是,如果在类型上定义了多个默认成员,GetDefaultMembers 就会引发 InvalidOperationException。以下代码示例显示 GetDefaultMembers 的语法。

[C#]
MemberInfo[] memInfo = t.GetDefaultMembers();
通过以 String.Empty ("") 为成员名称来调用 Type.InvokeMember,就可以调用默认成员。InvokeMember 将从类型中检索 DefaultMemberAttribute,然后调用它。

访问默认参数值
某些语言(如 C++ 托管扩展和 Microsoft Visual Basic .NET)支持将默认值赋给参数。例如,以下代码示例是一个合法的 Visual Basic .NET 声明,此声明将默认值赋给两个参数。

您可以使用参数属性来分配默认的参数值。

通过确切指定哪些参数是默认值或略去尾部的默认参数,可以声明参数的默认值。例如,以下所有代码示例都是对 MyMethod 的有效调用。

[C#]
MyMethod (10, 55.3, 12);
MyMethod (10, 1.3); // c == 1
MyMethod (11); // b == 1.2, c == 1
要使用反射检索参数的默认值,请获取该参数的 ParameterInfo 对象,然后使用 ParameterInfo.DefaultValue 属性检索默认值。如果不存在默认值,该属性将返回 Value.DBNull。

以下代码示例向控制台显示 MyMethod 的默认值。

[C#]
MethodInfo m = t.GetMethod ("MyMethod");
ParameterInfo[] ps = m.GetParameters();
for (int i = 0; i < ps.Length; i++) {
Console.WriteLine("Default Value == {0}", ps[i].DefaultValue);
}
要调用包含具有默认值的参数的方法,请使用 Type.Missing 作为 InvokeMember 方法的参数值。这样,晚期绑定服务就能够为指定的参数值使用默认值。如果为不带默认值的参数传递 Type.Missing,则将引发 ArgumentException。有一点务必要注意,并非所有编译器的绑定机制都会遵守 Type.Missing 的这些规则。有些联编程序可能不支持此功能,或者可能以不同的方式来处理 Type.Missing。当使用 Type.Missing 时,默认值不必是结尾的参数值。

C# 语言不支持默认参数。

以下 Visual Basic .NET 代码示例显示如何调用具有默认参数的方法。

[Visual Basic]
Option Strict Off
Imports System
Imports System.Reflection
Public Class OptionalArg
Public Sub MyMethod (a As Integer, Optional b As Double = 1.2, Optional c As Integer=1)
Console.WriteLine("a = " & a & " b = " & b & " c = " & c)
End Sub
End Class
Module Module1
Sub Main()
Dim o As New OptionalArg
Dim t As Type
t = GetType(OptionalArg)
Dim Param As Object()= {10, 20, 30}
t.InvokeMember("MyMethod", _
BindingFlags.Public Or _
BindingFlags.Instance Or _
BindingFlags.InvokeMethod Or _
BindingFlags.OptionalParamBinding, _
Nothing, _
o, _
New Object() {10, 55.3, 12})
t.InvokeMember("MyMethod", _
BindingFlags.Public Or _
BindingFlags.Instance Or _
BindingFlags.InvokeMethod Or _
BindingFlags.OptionalParamBinding, _
Nothing, _
o, _
New Object() {10, 1.3, Type.Missing})
t.InvokeMember("MyMethod", _
BindingFlags.Public Or _
BindingFlags.Instance Or _
BindingFlags.InvokeMethod Or _
BindingFlags.OptionalParamBinding, _
Nothing, _
o, _
New Object() {10, Type.Missing, Type.Missing})
End Sub
End Module
当使用上述方法时,即使调用方未指定任何值,仍会考虑尾部的默认参数。这是调用具有默认参数的方法时最常用的方式。

如果是使用 MethodBase.Invoke 来调用方法,则需要显式指定哪些参数是默认值,指定的方法是为所有没有值的参数传递一个包含 Type.Missing 的对象数组。

访问自定义属性
当属性与程序元素相关联后,可以使用反射来查询它们是否存在以及它们的值。用于查询属性的主要反射方法包含在 System.Reflection.MemberInfo.GetCustomAttributes 和 System.Reflection.Assembly.GetCustomAttributes 中。

自定义属性的可访问性根据附加该属性的程序集来进行检查。这相当于检查附加自定义属性的程序集中的类型上的方法是否可以调用自定义属性的构造函数。

诸如 System.Reflection.Assembly.GetCustomAttributes(Type, Boolean) 等方法检查类型参数的可见性和可访问性。只有包含用户定义类型的程序集中的代码才能使用 GetCustomAttributes 检索该类型的自定义属性。

以下代码示例是典型的自定义属性设计模式。它说明运行库自定义属性反射模型。

[C#]

System.DLL

public class DescriptionAttribute : Attribute

{

}



System.Web.DLL

internal class MyDescriptionAttribute : DescriptionAttribute

{

}



public class LocalizationExtenderProvider

{

[MyDescriptionAttribute(...)]

public CultureInfo GetLanguage(...)

{

}

}

如果试图为附加到 GetLanguage 方法的公共自定义属性类型 DescriptionAttribute 检索自定义属性,运行库将执行以下操作:

运行库检查 Type.GetCustomAttributes(Type type) 的 DescriptionAttribute 类型参数是否为公共的,并检查其是否可见或可以访问。
运行库检查从 DescriptionAttribute 导出的用户定义类型 MyDescriptionAttribute 在 System.Web.DLL 程序集(它在该程序集中附加到 GetLanguage() 方法)内是否可见和可以访问。
运行库检查 MyDescriptionAttribute 的构造函数是否在 System.Web.DLL 程序集中可见和可以访问。
运行库调用带有自定义属性参数的 MyDescriptionAttribute 的构造函数,然后将新对象返回给调用方。
自定义属性反射模型可能会在定义类型的程序集外泄漏用户定义类型的实例。这与运行库系统库中返回用户定义类型的实例的成员(例如返回 RuntimeMethodInfo 对象数组的 Type.GetMethods())相同。为了防止客户端发现关于用户定义的自定义属性类型的信息,请将该类型的成员定义为非公共成员。

以下代码示例说明使用反射访问自定义属性的基本方法。

[C#]

class MainClass

{

public static void Main()

{

System.Reflection.MemberInfo info = typeof(MyClass);

object[] attributes = info.GetCustomAttributes();

for (int i = 0; i < attributes.Length; i ++)

{

System.Console.WriteLine(attributes[i]);

}

}

}

指定完全限定的类型名称
要为各种反射操作提供有效的输入,必须指定类型名称。完全限定的类型名称包含程序集名称指定、命名空间指定和类型名称。类型名称指定将由 Type.GetType、Module.GetType、ModuleBuilder.GetType 和 Assembly.GetType 来使用。

类型名称的 Backus-Naur 形式语法
Backus-Naur 形式 (BNF) 定义正式语言的语法。下表中的 BNF 词法规则将说明如何识别有效的输入。最终元素(无法再减小的元素)将全部以大写字母显示。非最终元素(可以再减小的元素)则显示为大小写混合或带单引号的字符串,但单引号 (') 不是语法本身的一部分。管道字符 (|) 表示具有子规则的规则。

完全限定类型名称的 BNF 语法

TypeSpec := ReferenceTypeSpec

| SimpleTypeSpec

ReferenceTypeSpec := SimpleTypeSpec '&'

SimpleTypeSpec := PointerTypeSpec

| ArrayTypeSpec

| TypeName

PointerTypeSpec := SimpleTypeSpec '*'

ArrayTypeSpec := SimpleTypeSpec '[ReflectionDimension]'

| SimpleTypeSpec '[ReflectionEmitDimension]'

ReflectionDimension := '*'

| ReflectionDimension ',' ReflectionDimension

| NOTOKEN

ReflectionEmitDimension := '*'

| Number '..' Number

| Number '...'

| ReflectionDimension ',' ReflectionDimension

| NOTOKEN

Number := [0-9]+

TypeName := NamespaceTypeName

| NamespaceTypeName ',' AssemblyNameSpec

NamespaceTypeName := NestedTypeName

| NamespaceSpec '.'NestedTypeName

NestedTypeName := IDENTIFIER

| NestedTypeName '+' IDENTIFIER

NamespaceSpec := IDENTIFIER

| NamespaceSpec '.'IDENTIFIER

AssemblyNameSpec := IDENTIFIER

| IDENTIFIER ',' AssemblyProperties

AssemblyProperties := AssemblyProperty

| AssemblyProperties ',' AssemblyProperty

AssemblyProperty := AssemblyPropertyName '=' AssemblyPropertyValue


指定特殊字符
在 TypeName 中,IDENTIFIER 是由语言规则所确定的任何有效名称。

反斜杠 (\) 可用作转义符来分隔以下用作 IDENTIFIER 一部分的标记。

标记
含义

\,
程序集分隔符。

\+
嵌套类型分隔符。

\&
引用类型。

\*
指针类型。

\[
数组维度分隔符。

\]
数组维度分隔符。

\.
只有在数组指定中使用句点时,才应在句点前使用反斜杠。NamespaceSpec 中的句点不采用反斜杠。

\ 用作字符串的反斜杠。


请注意,在除 AssemblyNameSpec 之外的所有 TypeSpec 组成部分中,空格都是相关的。在 AssemblyNameSpec 中,“,”分隔符之前的空格相关,但“,”分隔符之后的空格将被忽略。

反射类(如 Type.FullName)将返回经过处理的名称,以便使返回的名称可以在对 GetType 的调用中使用(例如在 MyType.GetType(myType.FullName) 中)。

例如,某个类型的完全限定名称可能是 Ozzy.OutBack.Kangaroo+Wallaby,MyAssembly。

如果命名空间为 Ozzy.Out+Back,则必须在加号前加反斜杠。否则,分析器会将其解释为嵌套分隔符。反射会将该字符串当作 Ozzy.Out\+Back.Kangaroo+Wallaby,MyAssembly 发出。

指定程序集名称
程序集名称指定中所需的最少信息为程序集的文本名称 (IDENTIFIER)。您可以在 IDENTIFIER 后添加下表所述的逗号分隔属性/值对列表。IDENTIFIER 命名应遵循文件命名的规则。IDENTIFIER 不区分大小写。

有效的程序集属性

属性名称
说明
允许值

Version
程序集版本号
Major.Minor.Build.Revision,其中 Major、Minor、Build 和 Revision 是 0 和 65535 之间(含 0 和 65535)的整数。

PublicKey
完全公钥
完全公钥的十六进制字符串值。指定 null(在 Microsoft Visual Basic .NET 中为 Nothing)可显式地指示私有程序集。

PublicKeyToken
公钥标记(完全公钥的 8 字节哈希)
公钥标记的十六进制字符串值。指定 null(在 Visual Basic .NET 中为 Nothing)可显式地指示私有程序集。

Culture
程序集区域性
程序集的 RFC-1766 格式区域性,或者对于独立于语言(非附属)的程序集为“非特定”。

Custom
自定义的二进制大对象 (BLOB)。它当前仅用于由本机图像生成器 (Ngen.exe) 生成的程序集。
自定义的字符串,由本机图像生成器工具用来向程序集缓存通知所安装的程序集为本机图像,因此将安装在本机图像缓存中。也称作 Zap 字符串。


以下代码示例显示带有默认区域性且名称简单的程序集的 AssemblyName。

[C#]
com.microsoft.crypto, Culture=""
以下代码示例显示区域性为“en”且带有强名称的程序集的完全限定引用。

[C#]
com.microsoft.crypto, Culture=en, PublicKeyToken=a5d015c7d5a0b012,
Version=1.0.0.0
以下代码示例显示部分指定的 AssemblyName,它可以由带有强名称或简单名称的程序集来满足。

[C#]
com.microsoft.crypto
com.microsoft.crypto, Culture=""
com.microsoft.crypto, Culture=en
以下每个代码示例都显示一个部分指定的 AssemblyName,它必须由带有简单名称的程序集来满足。

[C#]
com.microsoft.crypto, Culture="", PublicKeyToken=null
com.microsoft.crypto, Culture=en, PublicKeyToken=null
以下每个代码示例都显示一个部分指定的 AssemblyName,它必须由带有强名称的程序集来满足。

[C#]
com.microsoft.crypto, Culture="", PublicKeyToken=a5d015c7d5a0b012
com.microsoft.crypto, Culture=en, PublicKeyToken=a5d015c7d5a0b012,
Version=1.0.0.0
指定指针
SimpleTypeSpec* 表示非托管指针。例如,要获取指向 MyType 类型的指针,请使用 Type.GetType("MyType*")。要获取指向 MyType 类型指针的指针,请使用 Type.GetType("MyType**")。

指定引用
SimpleTypeSpec & 表示托管指针或引用。例如,要获取对 MyType 类型的引用,请使用 Type.GetType("MyType &")。请注意,与指针不同,引用仅限于一个级别。

指定数组
在 BNF 语法中,ReflectionEmitDimension 仅适用于使用 ModuleBuilder.GetType 检索的不完整类型定义。不完整的类型定义是使用 Reflection.Emit 构造的 TypeBuilder 对象(但没有对这些对象调用 TypeBuilder.CreateType)。ReflectionDimension 可用于检索任何已完成的类型定义,即已加载的类型。

通过指定数组的秩,可以访问反射中的数组。

Type.GetType("MyArray[]") 获取下限为 0 的单维数组。
Type.GetType("MyArray[*]") 获取下限未知的单维数组。
Type.GetType("MyArray[][]") 获取二维数组的数组。
Type.GetType("MyArray[*,*]") 和 Type.GetType("MyArray[,]") 获取下限未知的矩形二维数组。
请注意,从运行库的角度来看,MyArray[] != MyArray[*],但对于多维数组而言,这两者表示是等效的。也就是说,Type.GetType("MyArray [,]") == Type.GetType("MyArray[*,*]") 的计算结果为 true。

对于 ModuleBuilder.GetType,MyArray[0..5] 指示大小为 6 下限为 0 的单维数组。MyArray[4...] 指示大小未知下限为 4 的单维数组。



<< Home

This page is powered by Blogger. Isn't yours?