反射(C# - vs2010)
反射提供了描述程序集、模块和类型的对象(Type 类型)。 可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。 如果代码中使用了特性,可以利用反射来访问它们。 有关更多信息,请参见 利用特性扩展元数据。
下面是使用静态方法 GetType(从 Object 基类派生的所有类型都继承该方法)获取变量类型的简单反射示例:
// Using GetType to obtain type information: int i = 42; System.Type type = i.GetType(); System.Console.WriteLine(type);
输出
System.Int32
下面的示例使用反射获取已加载的程序集的完整名称。
// Using Reflection to get information from an Assembly: System.Reflection.Assembly info = typeof(System.Int32).Assembly; System.Console.WriteLine(info);
输出
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
注意 |
---|
C# 关键字 protected 和 internal 在 IL 中没有意义并且在反射 API 中不使用。 IL 中对应的术语为“家族”和“程序集”。 若要使用反射来标识 internal 方法,请使用 IsAssembly 属性。 若要标识 protected internal 方法,请使用 IsFamilyOrAssembly。 |
反射在下列情况下很有用:
-
当需要访问程序元数据中的特性时。 有关更多信息,请参见 检索存储在特性中的信息。
-
检查和实例化程序集中的类型。
-
在运行时构建新类型。 使用 System.Reflection.Emit 中的类。
-
执行后期绑定,访问在运行时创建的类型的方法。 请参见主题动态加载和使用类型。
反射概述
通过 System.Reflection 命名空间中的类以及 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 发现以下信息:参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。
-
当您在一个应用程序域的仅反射上下文中工作时,请使用 CustomAttributeData 来发现有关自定义特性的信息。 通过使用 CustomAttributeData,您不必创建特性的实例就可以检查它们。
System.Reflection.Emit 命名空间的类提供了一种特殊形式的反射,使您能够在运行时生成类型。
反射也可用于创建称作类型浏览器的应用程序,它使用户能够选择类型,然后查看有关选定类型的信息。
反射还有其他一些用途。 JScript 等语言编译器使用反射来构造符号表。 System.Runtime.Serialization 命名空间中的类使用反射来访问数据并确定要持久保存的字段。 System.Runtime.Remoting 命名空间中的类通过序列化来间接地使用反射。
反射中的运行时类型
反射提供类(例如 Type 和 MethodInfo)来表示类型、成员、参数和其他代码实体。 但是,在您使用反射时,您并不直接使用这些类,这些类中的大多数是抽象的(在 Visual Basic 中为 MustInherit)。 您使用的是公共语言运行时 (CLR) 提供的类型。
例如,使用 C# 的 typeof 运算符(在 Visual Basic 中为 GetType)获取 Type 对象时,该对象实际上是 RuntimeType。 RuntimeType 派生自 Type,并提供所有抽象方法的实现。
这些运行时类是 internal(在 Visual Basic 中为 Friend)。 它们的文档与它们的基类的文档并没有分开,因为它们的行为是由基类文档描述的。
查看类型信息
System.Type 类对于反射起着核心的作用。 当反射请求加载的类型时,公共语言运行时将为它创建一个 Type。 您可以使用 Type 对象的方法、字段、属性和嵌套类来查找有关该类型的所有信息。
使用 Assembly.GetType 或 Assembly.GetTypes 从尚未加载的程序集中获取 Type 对象,并传入所需类型的名称。 使用 Type.GetType 可从已加载的程序集中获取 Type 对象。 使用 Module.GetType 和 Module.GetTypes 可获取模块 Type 对象。
注意 |
---|
如果想要检查和操作泛型类型和方法,请参见反射类型和泛型类型和如何:使用反射检查和实例化泛型类型中提供的附加信息。 |
下面的示例显示在获取程序集的 Assembly 对象和模块时所必需的语法。
// Gets the mscorlib assembly in which the object is defined. Assembly a = typeof(object).Module.Assembly;
下面的示例说明如何从已加载的程序集中获取 Type 对象。
// Loads an assembly using its file name. Assembly a = Assembly.LoadFrom("MyExe.exe"); // Gets 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 类)的构造函数。
// This program lists all the public constructors // of the System.String class. using System; using System.Reflection; class ListMembers { public static void Main() { 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 属性确定该类的可见性。
using System; using System.IO; using System.Reflection; class Mymemberinfo { public static void Main() { Console.WriteLine (" Reflection.MemberInfo"); // Gets the Type and MemberInfo. Type MyType = Type.GetType("System.IO.File"); MemberInfo[] Mymemberinfoarray = MyType.GetMembers(); // Gets and displays the DeclaringType method. Console.WriteLine(" There 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 类的一个成员执行反射,然后列出其类型。
// 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"); // Gets and displays the Type. Type MyType = Type.GetType("System.Reflection.FieldInfo"); // Specifies the member for which you want type information. MethodInfo Mymethodinfo = MyType.GetMethod("GetValue"); Console.WriteLine(MyType.FullName + "." + Mymethodinfo.Name); // Gets and displays 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 来列出指定类的所有成员(构造函数、字段、属性、事件和方法),并将这些成员划分为静态和实例类别。
// 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() { // Specifies the class. Type t = typeof(System.IO.BufferedStream); Console.WriteLine("Listing all the members (public and non public) of the {0} type", t); // Lists static fields 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(" Press ENTER to exit."); Console.Read(); } public static void PrintMembers (MemberInfo [] ms) { foreach (MemberInfo m in ms) { Console.WriteLine ("{0}{1}", " ", m); } Console.WriteLine(); } }
反射类型和泛型类型
从反射的角度来说,泛型类型和普通类型的区别在于泛型类型与一组类型参数(如果是泛型类型定义)或类型变量(如果是构造的类型)关联。 泛型方法与普通方法的区别也在于此。
要理解反射处理泛型类型和泛型方法的方式,有两点很重要:
-
泛型类型定义和泛型方法定义的类型参数是由 Type 类的实例表示的。
注意 如果 Type 对象表示泛型类型参数,则 Type 的许多属性和方法具有不同的行为。 这些不同在属性和方法主题中进行介绍。 有关示例,请参见 IsAutoClass 和 DeclaringType。 此外,某些成员仅当 Type 对象表示泛型类型参数时才有效。 有关示例,请参见 GetGenericTypeDefinition。
-
如果 Type 的实例表示泛型类型,则会包含一组表示类型参数(泛型类型定义)或类型变量(构造类型)的类型。 表示泛型方法的 MethodInfo 类的实例也是如此。
反射提供 Type 和 MethodInfo 方法,允许访问类型参数数组,还可以确定 Type 实例表示的是类型参数还是实际类型。
有关演示此处所讨论的方法的代码示例,请参见如何:使用反射检查和实例化泛型类型。
下面的讨论假定已熟悉了泛型术语,如类型参数、类型变量以及开放式或封闭式构造类型之间的差异。 有关更多信息,请参见 .NET Framework 中的泛型。
是泛型类型还是泛型方法?
使用反射检查 Type 实例表示的未知类型时,请使用 IsGenericType 属性确定未知类型是否为泛型。 如果是泛型类型,则返回 true。 类似地,检查 MethodInfo 类的实例表示的未知方法时,请使用 IsGenericMethod 属性确定方法是否为泛型。
是泛型类型定义还是泛型方法定义?
使用 IsGenericTypeDefinition 属性确定 Type 对象是否表示泛型类型定义,使用 IsGenericMethodDefinition 方法确定 MethodInfo 是否表示泛型方法定义。
泛型类型定义和泛型方法定义是一些模板,根据这些模板,可以创建可实例化的类型。 .NET Framework 类库中的泛型类型(如 Dictionary<TKey, TValue>)是泛型类型定义。
类型或方法是开放式的还是封闭式的?
如果可实例化类型的所有类型参数都已替换,包括所有封闭类型的所有类型参数,则泛型类型或泛型方法是封闭式的。 如果是封闭式的,则只能创建泛型类型的一个实例。 如果类型是开放式的,则 Type.ContainsGenericParameters 属性返回 true。 对于方法,MethodInfo.ContainsGenericParameters 方法执行同样的功能。
生成封闭式泛型类型
只要具有泛型类型定义或泛型方法定义,即可使用 MakeGenericType 方法创建封闭式泛型类型,或使用 MakeGenericMethod 方法为封闭式泛型方法创建 MethodInfo。
获取泛型类型定义或泛型方法定义
如果具有的开放式泛型类型或方法不是泛型类型定义或泛型方法定义,则不能创建它的实例,也不能提供缺少的类型参数。 必须具有泛型类型定义或泛型方法定义。 使用 GetGenericTypeDefinition 方法可获取泛型类型定义,使用 GetGenericMethodDefinition 方法可获取泛型方法定义。
例如,如果有表示 Dictionary<int, string>(在 Visual Basic 中为 Dictionary(Of Integer, String))的 Type 对象,并且希望创建 Dictionary<string, MyClass> 类型,则可以使用 GetGenericTypeDefinition 方法获取表示 Dictionary<TKey, TValue> 的 Type,然后使用 MakeGenericType 方法生成表示 Dictionary<int, MyClass> 的 Type。
有关不是泛型类型的开放式泛型类型的示例,请参见本主题后面部分的“是类型参数还是类型变量”。
检查类型变量和类型参数
使用 Type.GetGenericArguments 方法获取一组 Type 对象,这些对象表示泛型类型的类型参数或类型变量,对于泛型方法,使用 MethodInfo.GetGenericArguments 方法执行同样的操作。
只要了解 Type 对象表示类型参数,就可以回答许多其他问题。 可以确定类型参数的源、位置及其约束。
是类型参数还是类型变量
若要确定数组的特定元素是类型参数还是类型变量,请使用 IsGenericParameter 属性。 如果元素是类型参数,则 IsGenericParameter 属性为 true。
泛型类型可以是开放式的非泛型类型定义,在这种情况下,它同时具有类型变量和类型参数。 例如,在下面的代码中,派生出类 D 的类型是通过用 D 的第一个类型参数替换 B 的第二个类型参数创建的。
class B<T, U> {} class D<V, W> : B<int, V> {}
如果获取表示 D<V, W> 的 Type 对象并使用 BaseType 属性获取其基类型,则产生的 type B<int, V> 是开放式的非泛型类型定义。
泛型参数的源
泛型类型参数可能来自要检查的类型、封闭式类型或泛型方法。 可以确定泛型类型参数的源,方法如下:
-
首先,使用 DeclaringMethod 属性确定类型参数是否来自泛型方法。 如果该属性值不是空引用(在 Visual Basic 中为 Nothing),则源是泛型方法。
-
如果源不是泛型方法,则使用 DeclaringType 属性确定泛型类型参数所属的泛型类型。
如果类型参数属于泛型方法,则 DeclaringType 属性返回声明该泛型方法的类型,这已没有意义。
泛型参数的位置
在极少情况下,需要确定类型参数在其声明类的类型参数列表中的位置。 例如,假定具有一个 Type 对象,该对象表示上一示例中的 B<int, V> 类型。 GetGenericArguments 方法提供类型变量列表,在检查 V 时,可以使用 DeclaringMethod 和 DeclaringType 属性获取其来源。 然后,可以使用 GenericParameterPosition 属性确定其在定义时在类型参数列表中的位置。 在本示例中,V 在进行定义的类型参数列表的位置 0(零)处。
基类型约束和接口约束
使用 GetGenericParameterConstraints 方法可获取类型参数的基类型约束和接口约束。 数组元素的顺序不重要。 如果是接口类型,则元素表示接口约束。
泛型参数特性
GenericParameterAttributes 属性获取 GenericParameterAttributes 值,此值指示类型参数的变体(协变或逆变)和特殊约束。
协变和逆变
若要确定类型参数是协变还是逆变,请将 GenericParameterAttributes.VarianceMask 掩码应用于由 GenericParameterAttributes 属性返回的 GenericParameterAttributes 值。 如果结果为 GenericParameterAttributes.None,则类型参数为固定参数。 请参见泛型中的协变和逆变。
特殊约束
若要确定类型参数的特殊约束,请将 GenericParameterAttributes.SpecialConstraintMask 掩码应用于由 GenericParameterAttributes 属性返回的 GenericParameterAttributes 值。 如果结果为 GenericParameterAttributes.None,则没有任何特殊约束。 可以将类型参数约束为引用类型、不可为 null 的值类型和具有默认构造函数。
固定条件
有关泛型类型反射中常用术语的固定条件表,请参见 Type.IsGenericType。 有关泛型方法的其他相关术语,请参见 MethodInfo.IsGenericMethod。
如何:使用反射检查和实例化泛型类型
与其他类型的信息一样,泛型类型的信息的获取方式为:检查表示泛型类型的 Type 对象。 主要的差异在于,泛型类型具有一组表示其泛型类型参数的 Type 对象。 本部分的第一个步骤是检查泛型类型。
通过将类型变量绑定到泛型类型定义的类型参数,可以创建表示构造类型的 Type 对象。 第二个步骤演示此过程。
检查泛型类型及其类型参数
-
获取表示泛型类型的 Type 实例。 在下面的代码中,使用 C# 的 typeof 运算符(在 Visual Basic 中为 GetType,在 Visual C++ 中为 typeid)获取类型。 有关获取 Type 对象的其他方法,请参见 Type 类主题。 注意,余下的步骤中,类型包含在名为 t 的方法参数中。
Type d1 = typeof(Dictionary<,>);
-
使用 IsGenericType 属性确定类型是否为泛型,然后使用 IsGenericTypeDefinition 属性确定类型是否为泛型类型定义。
Console.WriteLine(" Is this a generic type? {0}", t.IsGenericType); Console.WriteLine(" Is this a generic type definition? {0}", t.IsGenericTypeDefinition);
-
使用 GetGenericArguments 方法获取包含泛型类型参数的数组。
Type[] typeParameters = t.GetGenericArguments();
-
对每个类型变量,使用 IsGenericParameter 属性确定其是不是类型参数(例如,在泛型类型定义中),是不是已为类型参数指定的类型(例如,在构造类型中)。
Console.WriteLine(" List {0} type arguments:", typeParameters.Length); foreach( Type tParam in typeParameters ) { if (tParam.IsGenericParameter) { DisplayGenericParameter(tParam); } else { Console.WriteLine(" Type argument: {0}", tParam); } }
-
在类型系统中,和普通类型一样,泛型类型参数是由 Type 的实例表示的。 下面的代码演示表示泛型类型参数的 Type 对象的名称和参数位置。 此处,参数位置无足轻重;它在检查类型参数(用作其他泛型类型的类型变量)时更有价值。
private static void DisplayGenericParameter(Type tp) { Console.WriteLine(" Type parameter: {0} position {1}", tp.Name, tp.GenericParameterPosition);
-
通过使用 GetGenericParameterConstraints 方法获取单个数组中的所有约束,确定泛型类型参数的基类型约束和接口约束。 不保证约束处于任何特定顺序。
Type classConstraint = null; foreach(Type iConstraint in tp.GetGenericParameterConstraints()) { if (iConstraint.IsInterface) { Console.WriteLine(" Interface constraint: {0}", iConstraint); } } if (classConstraint != null) { Console.WriteLine(" Base type constraint: {0}", tp.BaseType); } else Console.WriteLine(" Base type constraint: None");
-
使用 GenericParameterAttributes 属性获取类型参数的特殊约束,如要求其为引用类型。 该属性还包含表示方差的值,该值可屏蔽,如下面的代码所示。
GenericParameterAttributes sConstraints = tp.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
-
特殊约束的特性为标志,表示没有任何特殊约束的标志 (GenericParameterAttributes.None) 还表示没有协变或逆变。 因此,若要测试其中一个条件,必须使用适当的屏蔽。 在此情况下,请使用 GenericParameterAttributes.SpecialConstraintMask 隔离特殊约束标志。
if (sConstraints == GenericParameterAttributes.None) { Console.WriteLine(" No special constraints."); } else { if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.DefaultConstructorConstraint)) { Console.WriteLine(" Must have a parameterless constructor."); } if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.ReferenceTypeConstraint)) { Console.WriteLine(" Must be a reference type."); } if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint)) { Console.WriteLine(" Must be a non-nullable value type."); } }
构造泛型类型的实例
泛型类型和模板类似。 除非指定其泛型类型参数的实际类型,否则不能创建泛型类型的实例。 若要在运行时使用反射创建实例,需要使用 MakeGenericType 方法。
-
获取表示泛型类型的 Type 对象。 下面的代码以两种不同方式获取泛型类型 Dictionary<TKey, TValue>:一种方法使用 Type.GetType(String) 方法重载和描述类型的字符串,另一种方法调用构造类型 Dictionary<String, Example>(在 Visual Basic 中为 Dictionary(Of String, Example))的 GetGenericTypeDefinition 方法。 MakeGenericType 方法需要泛型类型定义。
// Use the typeof operator to create the generic type // definition directly. To specify the generic type definition, // omit the type arguments but retain the comma that separates // them. Type d1 = typeof(Dictionary<,>); // You can also obtain the generic type definition from a // constructed class. In this case, the constructed class // is a dictionary of Example objects, with String keys. Dictionary<string, Example> d2 = new Dictionary<string, Example>(); // Get a Type object that represents the constructed type, // and from that get the generic type definition. The // variables d1 and d4 contain the same type. Type d3 = d2.GetType(); Type d4 = d3.GetGenericTypeDefinition();
-
构造一组用于替换类型参数的类型变量。 数组必须包含正确数目的 Type 对象,并且顺序和对象在类型参数列表中的顺序相同。 在这种情况下,键(第一个类型参数)的类型为 String,字典中的值是名为 Example 的类的实例。
Type[] typeArgs = {typeof(string), typeof(Example)};
-
调用 MakeGenericType 方法将类型变量绑定到类型参数,然后构造类型。
Type constructed = d1.MakeGenericType(typeArgs);
-
使用 CreateInstance(Type) 方法重载来创建构造类型的对象。 下面的代码在生成的 Dictionary<String, Example> 对象中存储 Example 类的两个实例。
object o = Activator.CreateInstance(constructed);
示例
下面的代码示例定义 DisplayGenericType 方法来检查泛型类型定义和代码中使用的构造类型,并显示它们的信息。 DisplayGenericType 方法演示如何使用 IsGenericType、IsGenericParameter 和 GenericParameterPosition 属性以及 GetGenericArguments 方法。
该示例还定义 DisplayGenericParameter 方法来检查泛型类型参数并显示其约束。
代码示例定义一组测试类型,包括说明类型参数约束的泛型类型,并演示如何显示这些类型的信息。
示例通过创建一组类型参数并调用 MakeGenericType 方法,从 Dictionary<TKey, TValue> 类构造类型。 程序对使用 MakeGenericType 构造的 Type 对象和使用 typeof(Visual Basic 中为 GetType)获取的 Type 对象进行比较,演示这两个对象是相同的。 类似地,程序使用 GetGenericTypeDefinition 方法获取构造类型的泛型类型定义,并将其与表示 Dictionary<TKey, TValue> 类的 Type 对象进行比较。
using System; using System.Reflection; using System.Collections.Generic; using System.Security.Permissions; // Define an example interface. public interface ITestArgument {} // Define an example base class. public class TestBase {} // Define a generic class with one parameter. The parameter // has three constraints: It must inherit TestBase, it must // implement ITestArgument, and it must have a parameterless // constructor. public class Test<T> where T : TestBase, ITestArgument, new() {} // Define a class that meets the constraints on the type // parameter of class Test. public class TestArgument : TestBase, ITestArgument { public TestArgument() {} } public class Example { // The following method displays information about a generic // type. private static void DisplayGenericType(Type t) { Console.WriteLine(" {0}", t); Console.WriteLine(" Is this a generic type? {0}", t.IsGenericType); Console.WriteLine(" Is this a generic type definition? {0}", t.IsGenericTypeDefinition); // Get the generic type parameters or type arguments. Type[] typeParameters = t.GetGenericArguments(); Console.WriteLine(" List {0} type arguments:", typeParameters.Length); foreach( Type tParam in typeParameters ) { if (tParam.IsGenericParameter) { DisplayGenericParameter(tParam); } else { Console.WriteLine(" Type argument: {0}", tParam); } } } // The following method displays information about a generic // type parameter. Generic type parameters are represented by // instances of System.Type, just like ordinary types. private static void DisplayGenericParameter(Type tp) { Console.WriteLine(" Type parameter: {0} position {1}", tp.Name, tp.GenericParameterPosition); Type classConstraint = null; foreach(Type iConstraint in tp.GetGenericParameterConstraints()) { if (iConstraint.IsInterface) { Console.WriteLine(" Interface constraint: {0}", iConstraint); } } if (classConstraint != null) { Console.WriteLine(" Base type constraint: {0}", tp.BaseType); } else Console.WriteLine(" Base type constraint: None"); GenericParameterAttributes sConstraints = tp.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask; if (sConstraints == GenericParameterAttributes.None) { Console.WriteLine(" No special constraints."); } else { if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.DefaultConstructorConstraint)) { Console.WriteLine(" Must have a parameterless constructor."); } if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.ReferenceTypeConstraint)) { Console.WriteLine(" Must be a reference type."); } if (GenericParameterAttributes.None != (sConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint)) { Console.WriteLine(" Must be a non-nullable value type."); } } } [PermissionSetAttribute(SecurityAction.Demand, Name="FullTrust")] public static void Main() { // Two ways to get a Type object that represents the generic // type definition of the Dictionary class. // // Use the typeof operator to create the generic type // definition directly. To specify the generic type definition, // omit the type arguments but retain the comma that separates // them. Type d1 = typeof(Dictionary<,>); // You can also obtain the generic type definition from a // constructed class. In this case, the constructed class // is a dictionary of Example objects, with String keys. Dictionary<string, Example> d2 = new Dictionary<string, Example>(); // Get a Type object that represents the constructed type, // and from that get the generic type definition. The // variables d1 and d4 contain the same type. Type d3 = d2.GetType(); Type d4 = d3.GetGenericTypeDefinition(); // Display information for the generic type definition, and // for the constructed type Dictionary<String, Example>. DisplayGenericType(d1); DisplayGenericType(d2.GetType()); // Construct an array of type arguments to substitute for // the type parameters of the generic Dictionary class. // The array must contain the correct number of types, in // the same order that they appear in the type parameter // list of Dictionary. The key (first type parameter) // is of type string, and the type to be contained in the // dictionary is Example. Type[] typeArgs = {typeof(string), typeof(Example)}; // Construct the type Dictionary<String, Example>. Type constructed = d1.MakeGenericType(typeArgs); DisplayGenericType(constructed); object o = Activator.CreateInstance(constructed); Console.WriteLine(" Compare types obtained by different methods:"); Console.WriteLine(" Are the constructed types equal? {0}", (d2.GetType()==constructed)); Console.WriteLine(" Are the generic definitions equal? {0}", (d1==constructed.GetGenericTypeDefinition())); // Demonstrate the DisplayGenericType and // DisplayGenericParameter methods with the Test class // defined above. This shows base, interface, and special // constraints. DisplayGenericType(typeof(Test<>)); } }
编译代码
-
代码包含编译所需的 C# using 语句(在 Visual Basic 中为 Imports)。
-
不需要其他程序集引用。
-
在命令行使用 csc.exe、vbc.exe 或 cl.exe 编译代码。 若要在 Visual Studio 中编译代码,请将代码置于控制台应用程序项目模板中。
反射类所使用的设计模式
System.Reflection 命名空间中最常用的方法都使用统一的模式。 Module 、Type 和 MemberInfo 类的成员使用下表中所示的设计模式。
成员签名 |
说明 |
---|---|
MyInstance[] FindXxx(filter, filterCriteria) |
查找并返回经过筛选的类型列表,或者在当前类型没有实现任何匹配筛选器的类型的情况下返回空数组。 |
MyInstance GetXxx(<parameters>) |
返回由 <parameters> 唯一指定的类型。 如果不存在这样的类型,成员将返回空引用(在 Visual Basic 中为 Nothing)。 请注意,<parameters> 唯一地指定一个实例。 |
MyInstance[] GetXxxs() |
返回所有公共类型。 如果不存在公共类型,成员将返回空数组。 |
MyInstance[] GetXxxs(<parameters>) |
返回由 <parameters> 指定的所有类型。 如果不存在这样的类型,成员将返回空数组。 请注意,<parameters> 并不一定指定唯一的实例。 |
另一个常用的设计模式是使用委托。 它们通常在反射中用来支持对返回对象数组的方法的结果集进行筛选。
反射的安全注意事项
反射提供了获取有关类型和成员的信息和访问成员(即,调用方法和构造函数、获取和设置属性值、添加和移除事件处理程序等)的能力。 使用反射获取有关类型和成员的信息不受限制。 所有代码都可以使用反射来执行以下任务:
-
枚举类型和成员并验证其元数据。
-
枚举并验证程序集和模块。
相反,使用反射访问成员将受到限制。 从 .NET Framework 4 版开始,仅受信任的代码可以使用反射访问安全关键成员。而且,仅受信任的代码才能使用反射访问编译代码不可直接访问的非公共成员。最后,使用反射访问安全关键成员的代码必须具有安全关键成员要求的任何权限,就像使用编译代码一样。
根据必要的权限,代码可以使用反射来执行以下各种访问:
-
访问不是安全关键的公共成员。
-
访问可由已编译代码访问的非公共成员(如果这些成员不是安全关键)。 此类非公共成员的示例包括:
-
调用代码的基类的受保护成员。 (在反射中,将此称为系列级访问。)
-
调用代码的程序集中的 internal 成员(在 Visual Basic 中为 Friend 成员)。 (在反射中,将此称为程序集级访问。)
-
包含调用代码的类的其他实例的私有成员。
-
例如,在沙盒应用程序域中运行的代码只限于此列表中描述的访问,除非应用程序域授予其他权限。
从 .NET Framework 2.0 版 Service Pack 1 开始,如果尝试访问通常不可访问的成员,则会生成针对目标对象的授予集以及带有 ReflectionPermissionFlag.MemberAccess 标志的 ReflectionPermission 的要求。 以完全信任方式运行的代码(例如,从命令行启动的应用程序中的代码)始终可以满足这些权限。 (这会受到有关访问安全关键的成员的限制,如本文后面所述。)
或者,沙盒应用程序域可以授予带有 ReflectionPermissionFlag.MemberAccess 标志的 ReflectionPermission,如本文后面的访问通常不可访问的成员节中所述。
访问安全关键成员
如果一个成员满足以下条件之一,则为安全关键成员:具有 SecurityCriticalAttribute;属于一个具有 SecurityCriticalAttribute 的类型;或者位于一个安全关键程序集中。 从 .NET Framework 4 版开始,访问安全关键成员的规则如下所示:
-
透明代码不能使用反射来访问安全关键成员,即使代码是完全受信任的也是如此。 将引发 MethodAccessException、FieldAccessException 或 TypeAccessException。
-
以部分信任方式运行的代码将被视为透明的。
无论安全关键成员是由已编辑代码直接访问,还是通过使用反射访问,这些规则都是相同的。
从命令行运行的应用程序代码以完全信任方式运行。 只要代码不被标记为透明的,它就可以使用反射来访问安全关键成员。 当相同的代码以部分信任方式运行(例如,在沙盒应用程序域中)时,程序集的信任级别将确定它是否可以访问安全关键代码:如果程序集具有强名称并在全局程序集缓存中进行安装,则它是一个受信任的程序集并可以调用安全关键成员。 如果它不是受信任的,则它将成为透明的(即使它未被标记为透明的),并且不能访问安全关键成员。
有关 .NET Framework 4 中的安全模型的更多信息,请参见 .NET Framework 4 中的安全性更改。
反射和透明度
从 .NET Framework 4 开始,公共语言运行时将根据多个因素来确定类型或成员的透明度级别,这些因素包括程序集的信任级别和应用程序域的信任级别。 反射提供 IsSecurityCritical、IsSecuritySafeCritical 和 IsSecurityTransparent 属性以使您能够发现类型的透明度级别。 下表显示了这些属性的有效组合。
安全级别 |
IsSecurityCritical |
IsSecuritySafeCritical |
IsSecurityTransparent |
---|---|---|---|
Critical |
true |
false |
false |
安全关键的 |
true |
true |
false |
透明 |
false |
false |
true |
相对于检查程序集及其类型的安全批注、检查当前信任级别以及尝试复制运行时的规则,使用这些属性要简单很多。 例如,同一个类型既可以是安全关键的(当从命令行中运行时),也可以是安全透明的(当在沙盒应用程序域中运行时)。
MethodBase 、FieldInfo、TypeBuilder、MethodBuilder 和 DynamicMethod 类有相似的属性。 (对于其他反射和反射发出抽象,将对关联的方法应用安全特性。例如,对于属性,安全特性将应用于属性访问器。)
访问通常不可访问的成员
若要使用反射来调用根据公共语言运行时的可访问性规则不可访问的成员,必须向您的代码授予以下两种权限之一:
-
若要允许代码调用任何非公共成员,必须为您的代码授予带有 ReflectionPermissionFlag.MemberAccess 标志的 ReflectionPermission。
注意 默认情况下,安全策略拒绝向源自 Internet 的代码授予此权限。 决不应向源自 Internet 的代码授予此权限。
-
若要允许代码调用任何非公共成员(前提是包含被调用成员的程序集的授予集与包含调用代码的程序集的授予集或其子集相同),必须为您的代码授予带有 ReflectionPermissionFlag.RestrictedMemberAccess 标志的 ReflectionPermission。
例如,假定向某个应用程序域授予 Internet 权限以及带有 ReflectionPermissionFlag.RestrictedMemberAccess 标记的 ReflectionPermission,然后运行具有 A 和 B 两个程序集的 Internet 应用程序。
-
因为程序集 B 的授权集不包含任何未授予 A 的权限,因此程序集 A 可以使用反射访问程序集 B 的私有成员。
-
程序集 A 不能使用反射来访问 .NET Framework 程序集(例如 mscorlib.dll)的私有成员,其原因是:mscorlib.dll 是完全受信任的,因此它具有尚未授予程序集 A 的权限。 当代码访问安全性在运行时审核堆栈时,将引发 MemberAccessException。
序列化
对于序列化,带有 SecurityPermissionAttribute.SerializationFormatter 标志的 SecurityPermission 提供获取并设置可序列化类型的成员的功能,无论这些可序列化类型的成员是否可以访问。 此权限可以使代码发现并更改实例的私有状态。 (除了授予正确的权限之外,还必须在元数据中将类型标记为可序列化。)
类型 MethodInfo 的参数
应避免编写采用 MethodInfo 参数的公共成员,对于受信任的代码尤其如此。 此类成员可能更容易受到恶意代码的攻击。 例如,设想在高度受信任的代码中有一个采用 MethodInfo 参数的公共成员。 假设此公共成员对所提供的参数间接地调用 Invoke 方法。 如果公共成员没有执行必要的权限检查,由于安全系统断定调用方受到高度信任,对 Invoke 方法的调用将始终会成功。 即使恶意代码无权直接调用该方法,它仍然能够通过调用公共成员来间接地调用该方法。
版本信息
-
从 .NET Framework 4 版开始,透明代码将不能使用反射来访问安全关键成员。
-
ReflectionPermissionFlag.RestrictedMemberAccess 标志是在 .NET Framework 2.0 版 Service Pack 1 中引入的。 对于使用反射访问非公共成员的代码,早期版本的 .NET Framework 需要 ReflectionPermissionFlag.MemberAccess 标志。 决不应将此权限授予部分受信任的代码。
-
从 .NET Framework 2.0 开始,使用反射获取有关非公共类型和成员的信息不需要任何权限。 在早期版本中,带有 ReflectionPermissionFlag.TypeInformation 标志的 ReflectionPermission 是必需的。
动态加载和使用类型
反射提供语言编译器(如 Microsoft Visual Basic 2005 和 JScript)用于实现隐式后期绑定的基础结构。 绑定是查找与唯一指定的类型相对应的声明(即实现)的过程。 如果此过程是在运行时而不是在编译时发生,则称其为“后期绑定”。 利用 Visual Basic 2005,可以在代码中使用隐式后期绑定;Visual Basic 编译器会调用一个帮助器方法,该方法使用反射来获取对象类型。 传递给帮助器方法的参数有助于在运行时调用正确的方法。 这些参数包括:对其调用方法的实例(对象),被调用方法的名称(字符串),以及传递给被调用方法的参数(对象数组)。
在下面的示例中,Visual Basic 编译器使用反射隐式地对其类型在编译时未知的对象调用方法。 HelloWorld 类具有一个 PrintHello 方法,它输出与传递给 PrintHello 方法的某些文本串联的“Hello World”。 在此示例中调用的 PrintHello 方法实际上是 Type.InvokeMember;Visual Basic 代码允许按照对象 (helloObj) 的类型在编译时已知(早期绑定)而不是在运行时已知(后期绑定)的方式来调用 PrintHello 方法。
Imports System Module Hello Sub Main() ' Sets up the variable. Dim helloObj As Object ' Creates the object. helloObj = new HelloWorld() ' Invokes the print method as if it was early bound ' even though it is really late bound. helloObj.PrintHello("Visual Basic Late Bound") End Sub End Module
自定义绑定
除了由编译器隐式地用来进行后期绑定之外,反射还可以在代码中显式地用来完成后期绑定。
公共语言运行时支持多种编程语言,但这些语言的绑定规则各不相同。 在早期绑定的情况下,代码生成器可以完全控制此绑定。 但是,当通过反射进行后期绑定时,必须用自定义绑定来控制绑定。 Binder 类提供了对成员选择和调用的自定义控制。
利用自定义绑定,您可以在运行时加载程序集,获取有关该程序集中类型的信息,然后对该类型调用方法或访问该类型的字段或属性。 如果您在编译时(例如当对象类型依赖于用户输入时)不知道对象的类型,就可以使用这种方法。
下面的示例说明不提供参数类型转换的简单的自定义联编程序。 Simple_Type.dll 的代码位于示例主体之前。 确保生成 Simple_Type.dll,然后在生成时在项目中包括对它的引用。
// Code for building SimpleType.dll. using System; using System.Reflection; using System.Globalization; using Simple_Type; namespace Simple_Type { public class MySimpleClass { public void MyMethod(string str, int i) { Console.WriteLine("MyMethod parameters: {0}, {1}", str, i); } public void MyMethod(string str, int i, int j) { Console.WriteLine("MyMethod parameters: {0}, {1}, {2}", str, i, j); } } } namespace Custom_Binder { class MyMainClass { static void Main() { // Get the type of MySimpleClass. Type myType = typeof(MySimpleClass); // Get an instance of MySimpleClass. MySimpleClass myInstance = new MySimpleClass(); MyCustomBinder myCustomBinder = new MyCustomBinder(); // Get the method information for the particular overload // being sought. MethodInfo myMethod = myType.GetMethod("MyMethod", BindingFlags.Public | BindingFlags.Instance, myCustomBinder, new Type[] {typeof(string), typeof(int)}, null); Console.WriteLine(myMethod.ToString()); // Invoke the overload. myType.InvokeMember("MyMethod", BindingFlags.InvokeMethod, myCustomBinder, myInstance, new Object[] {"Testing...", (int)32}); } } // **************************************************** // A simple custom binder that provides no // argument type conversion. // **************************************************** class MyCustomBinder : Binder { public override MethodBase BindToMethod( BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] names, out object state) { if (match == null) { throw new ArgumentNullException("match"); } // Arguments are not being reordered. state = null; // Find a parameter match and return the first method with // parameters that match the request. foreach (MethodBase mb in match) { ParameterInfo[] parameters = mb.GetParameters(); if (ParametersMatch(parameters, args)) { return mb; } } return null; } public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, CultureInfo culture) { if (match == null) { throw new ArgumentNullException("match"); } foreach (FieldInfo fi in match) { if (fi.GetType() == value.GetType()) { return fi; } } return null; } public override MethodBase SelectMethod( BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) { if (match == null) { throw new ArgumentNullException("match"); } // Find a parameter match and return the first method with // parameters that match the request. foreach (MethodBase mb in match) { ParameterInfo[] parameters = mb.GetParameters(); if (ParametersMatch(parameters, types)) { return mb; } } return null; } public override PropertyInfo SelectProperty( BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers) { if (match == null) { throw new ArgumentNullException("match"); } foreach (PropertyInfo pi in match) { if (pi.GetType() == returnType && ParametersMatch(pi.GetIndexParameters(), indexes)) { return pi; } } return null; } public override object ChangeType( object value, Type myChangeType, CultureInfo culture) { try { object newType; newType = Convert.ChangeType(value, myChangeType); return newType; } // Throw an InvalidCastException if the conversion cannot // be done by the Convert.ChangeType method. catch (InvalidCastException) { return null; } } public override void ReorderArgumentArray(ref object[] args, object state) { // No operation is needed here because BindToMethod does not // reorder the args array. The most common implementation // of this method is shown below. // ((BinderState)state).args.CopyTo(args, 0); } // Returns true only if the type of each object in a matches // the type of each corresponding object in b. private bool ParametersMatch(ParameterInfo[] a, object[] b) { if (a.Length != b.Length) { return false; } for (int i = 0; i < a.Length; i++) { if (a[i].ParameterType != b[i].GetType()) { return false; } } return true; } // Returns true only if the type of each object in a matches // the type of each corresponding entry in b. private bool ParametersMatch(ParameterInfo[] a, Type[] b) { if (a.Length != b.Length) { return false; } for (int i = 0; i < a.Length; i++) { if (a[i].ParameterType != b[i]) { return false; } } return true; } } }
InvokeMember 和 CreateInstance
使用 Type.InvokeMember 可调用类型的成员。 各个类(如 System.Activator 和 System.Reflection.Assembly)的 CreateInstance 方法是 InvokeMember 的特殊形式,它们可新建特定类型的实例。 Binder 类用于在这些方法中进行重载决策和参数强制。
下面的示例显示参数强制(类型转换)和成员选择的三种可能的组合。 在第 1 种情况中,不需要任何参数强制或成员选择。 在第 2 种情况中,只需要成员选择。 在第 3 种情况中,只需要参数强制。
public class CustomBinderDriver { public static void Main() { Type t = typeof(CustomBinderDriver); CustomBinder binder = new CustomBinder(); BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static; object[] args; // 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 引用(在 Visual Basic 中为 Nothing)。 虽然 MethodBase 返回值通常是 match 参数中所包含的值之一,但它并不必如此。
当存在 ByRef 参数时,调用方可能需要取回这些参数。 因此,如果 BindToMethod 已经操作参数数组,Binder 会允许客户端将参数数组映射回它的初始形式。 为了实现这一目的,必须向调用方保证参数的顺序不会改变。 当按名称传递参数时,Binder 将重新排列参数数组,这就是调用方所见的参数。 有关更多信息,请参见 Binder.ReorderArgumentArray。
可用成员集包括在类型和任何基类型中定义的成员。 如果指定 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 进行回调以选择适当方法的给定签名信息。
如何:将程序集加载到仅反射上下文中
只反射加载上下文使您能够检查为其他平台或 .NET Framework 的其他版本编译的程序集。 加载到此上下文中的代码只能检查,不能执行。 这意味着无法创建对象,因为无法执行构造函数。 因为该代码无法执行,所以不会自动加载依赖项。 如果需要对依赖项进行检查,则必须自行加载。
将程序集加载到只反射加载上下文中
-
使用 ReflectionOnlyLoad(String) 方法重载可加载给定了显示名称的程序集,而使用 ReflectionOnlyLoadFrom 方法可加载给定了路径的程序集。 如果该程序集为二进制文件映像,则使用 ReflectionOnlyLoad(Byte[]) 方法重载。
注意 您不能使用只反射上下文从非执行上下文中的 .NET Framework 版本加载 mscorlib.dll 版本。
-
如果该程序集具有依赖项,ReflectionOnlyLoad 方法不会加载这些依赖项。 如果需要对依赖项进行检查,则必须自行加载。
-
使用程序集的 ReflectionOnly 属性确定是否将该程序集加载到了只反射上下文中。
-
如果向该程序集或程序集中的类型应用了特性,则应通过使用 CustomAttributeData 类检查这些特性,以确保未尝试在只反射上下文中执行代码。 使用 CustomAttributeData.GetCustomAttributes 方法的适当重载可获取表示应用于程序集、成员、模块或参数的特性的 CustomAttributeData 对象。
注意 应用于该程序集或其内容的特性可能是在该程序集中定义的,也可能是在加载到只反射上下文中的另一个程序集中定义的。 无法事先知道这些特性是在何处定义的。
示例
下面的代码示例演示如何检查应用于加载到只反射上下文中的程序集的特性。
该代码示例定义了一个带有两个构造函数和一个属性的自定义特性。 该特性可应用于程序集、在该程序集中声明的类型、该类型的方法以及该方法的参数。 在执行时,该程序集将其本身加载到只反射上下文中,并显示应用到它及它包含的类型和成员的自定义特性的有关信息。
注意 |
---|
为简化该代码示例,该程序集自行完成加载和检查操作。 通常情况下,不要在执行上下文和只反射上下文中加载同一程序集。 |
using System; using System.Reflection; using System.Collections.Generic; using System.Collections.ObjectModel; // The example attribute is applied to the assembly. [assembly:Example(ExampleKind.ThirdKind, Note="This is a note on the assembly.")] // An enumeration used by the ExampleAttribute class. public enum ExampleKind { FirstKind, SecondKind, ThirdKind, FourthKind }; // An example attribute. The attribute can be applied to all // targets, from assemblies to parameters. // [AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute { // Data for properties. private ExampleKind kindValue; private string noteValue; private string[] arrayStrings; private int[] arrayNumbers; // Constructors. The parameterless constructor (.ctor) calls // the constructor that specifies ExampleKind and an array of // strings, and supplies the default values. // public ExampleAttribute(ExampleKind initKind, string[] initStrings) { kindValue = initKind; arrayStrings = initStrings; } public ExampleAttribute(ExampleKind initKind) : this(initKind, null) {} public ExampleAttribute() : this(ExampleKind.FirstKind, null) {} // Properties. The Note and Numbers properties must be read/write, so they // can be used as named parameters. // public ExampleKind Kind { get { return kindValue; }} public string[] Strings { get { return arrayStrings; }} public string Note { get { return noteValue; } set { noteValue = value; } } public int[] Numbers { get { return arrayNumbers; } set { arrayNumbers = value; } } } // The example attribute is applied to the test class. // [Example(ExampleKind.SecondKind, new string[] { "String array argument, line 1", "String array argument, line 2", "String array argument, line 3" }, Note="This is a note on the class.", Numbers = new int[] { 53, 57, 59 })] public class Test { // The example attribute is applied to a method, using the // parameterless constructor and supplying a named argument. // The attribute is also applied to the method parameter. // [Example(Note="This is a note on a method.")] public void TestMethod([Example] object arg) { } // Main() gets objects representing the assembly, the test // type, the test method, and the method parameter. Custom // attribute data is displayed for each of these. // public static void Main() { Assembly asm = Assembly.ReflectionOnlyLoad("Source"); Type t = asm.GetType("Test"); MethodInfo m = t.GetMethod("TestMethod"); ParameterInfo[] p = m.GetParameters(); Console.WriteLine(" Attributes for assembly: '{0}'", asm); ShowAttributeData(CustomAttributeData.GetCustomAttributes(asm)); Console.WriteLine(" Attributes for type: '{0}'", t); ShowAttributeData(CustomAttributeData.GetCustomAttributes(t)); Console.WriteLine(" Attributes for member: '{0}'", m); ShowAttributeData(CustomAttributeData.GetCustomAttributes(m)); Console.WriteLine(" Attributes for parameter: '{0}'", p); ShowAttributeData(CustomAttributeData.GetCustomAttributes(p[0])); } private static void ShowAttributeData( IList<CustomAttributeData> attributes) { foreach( CustomAttributeData cad in attributes ) { Console.WriteLine(" {0}", cad); Console.WriteLine(" Constructor: '{0}'", cad.Constructor); Console.WriteLine(" Constructor arguments:"); foreach( CustomAttributeTypedArgument cata in cad.ConstructorArguments ) { ShowValueOrArray(cata); } Console.WriteLine(" Named arguments:"); foreach( CustomAttributeNamedArgument cana in cad.NamedArguments ) { Console.WriteLine(" MemberInfo: '{0}'", cana.MemberInfo); ShowValueOrArray(cana.TypedValue); } } } private static void ShowValueOrArray(CustomAttributeTypedArgument cata) { if (cata.Value.GetType() == typeof(ReadOnlyCollection<CustomAttributeTypedArgument>)) { Console.WriteLine(" Array of '{0}':", cata.ArgumentType); foreach (CustomAttributeTypedArgument cataElement in (ReadOnlyCollection<CustomAttributeTypedArgument>) cata.Value) { Console.WriteLine(" Type: '{0}' Value: '{1}'", cataElement.ArgumentType, cataElement.Value); } } else { Console.WriteLine(" Type: '{0}' Value: '{1}'", cata.ArgumentType, cata.Value); } } } /* This code example produces output similar to the following: Attributes for assembly: 'source, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' [System.Runtime.CompilerServices.CompilationRelaxationsAttribute((Int32)8)] Constructor: 'Void .ctor(Int32)' Constructor arguments: Type: 'System.Int32' Value: '8' Named arguments: [System.Runtime.CompilerServices.RuntimeCompatibilityAttribute(WrapNonExceptionThrows = True)] Constructor: 'Void .ctor()' Constructor arguments: Named arguments: MemberInfo: 'Boolean WrapNonExceptionThrows' Type: 'System.Boolean' Value: 'True' [ExampleAttribute((ExampleKind)2, Note = "This is a note on the assembly.")] Constructor: 'Void .ctor(ExampleKind)' Constructor arguments: Type: 'ExampleKind' Value: '2' Named arguments: MemberInfo: 'System.String Note' Type: 'System.String' Value: 'This is a note on the assembly.' Attributes for type: 'Test' [ExampleAttribute((ExampleKind)1, new String[3] { "String array argument, line 1", "String array argument, line 2", "String array argument, line 3" }, Note = "This is a note on the class.", Numbers = new Int32[3] { 53, 57, 59 })] Constructor: 'Void .ctor(ExampleKind, System.String[])' Constructor arguments: Type: 'ExampleKind' Value: '1' Array of 'System.String[]': Type: 'System.String' Value: 'String array argument, line 1' Type: 'System.String' Value: 'String array argument, line 2' Type: 'System.String' Value: 'String array argument, line 3' Named arguments: MemberInfo: 'System.String Note' Type: 'System.String' Value: 'This is a note on the class.' MemberInfo: 'Int32[] Numbers' Array of 'System.Int32[]': Type: 'System.Int32' Value: '53' Type: 'System.Int32' Value: '57' Type: 'System.Int32' Value: '59' Attributes for member: 'Void TestMethod(System.Object)' [ExampleAttribute(Note = "This is a note on a method.")] Constructor: 'Void .ctor()' Constructor arguments: Named arguments: MemberInfo: 'System.String Note' Type: 'System.String' Value: 'This is a note on a method.' Attributes for parameter: 'System.Object arg' [ExampleAttribute()] Constructor: 'Void .ctor()' Constructor arguments: Named arguments: */
访问默认成员
任何类型都可以具有默认成员,即在未给定任何成员名称时调用的成员。 可通过调用 Type.InvokeMember 方法(以 String.Empty ("") 作为成员名称)来调用默认成员。 InvokeMember 将从类型中检索 System.Reflection.DefaultMemberAttribute 特性,然后调用它。 下面的示例调用 Class1 的默认成员,并将它返回的值赋给 o。
Class1 c = new Class1(); object o; o = c.GetType().InvokeMember("", BindingFlags.InvokeMethod, null, c, new object[0]); Console.WriteLine("Default member result: {0}", o);
默认成员由声明类型上的 DefaultMemberAttribute 特性表示。 下面示例中显示的类手动添加了 DefaultMemberAttribute。 如果类声明了索引器,请勿手动添加 DefaultMemberAttribute;在该情况下,编译器会自动添加该特性。
[DefaultMember("GetIVal")] public class Class1 { private int ival; private string sval; public Class1() { ival = 5050; sval = "6040"; } public int GetIVal() { return ival; } public string GetSVal() { return sval; } }
下面的示例显示如何通过检索默认成员的自定义特性来检索默认成员。
Type classType = typeof(Class1); Type attribType = typeof(DefaultMemberAttribute); DefaultMemberAttribute defMem = (DefaultMemberAttribute)Attribute.GetCustomAttribute((MemberInfo)classType, attribType); MemberInfo[] memInfo = classType.GetMember(defMem.MemberName);
使用 Type.GetDefaultMembers 方法会生成相同的结果,但可能更简单一些。 但是,如果在类型上定义了多个默认成员,则 GetDefaultMembers 会引发 InvalidOperationException。 下面的示例演示 GetDefaultMembers 的语法。
Type t = typeof(Class1);
MemberInfo[] memInfo = t.GetDefaultMembers();
您还可以使用 GetCustomAttributes 方法获得类型的自定义特性,并仅选择 DefaultMemberAttribute。 下面的示例演示这一技术。
Type t = typeof(Class1); object[] customAttribs = t.GetCustomAttributes(typeof(DefaultMemberAttribute), false); if (customAttribs.Length > 0) { DefaultMemberAttribute defMem = (DefaultMemberAttribute)customAttribs[0]; MemberInfo[] memInfo = t.GetMember(defMem.MemberName); if (memInfo.Length > 0) { Console.WriteLine("Default Member: {0}", memInfo[0].Name); } }
访问默认参数值
某些语言(如 Visual C++ 和 Microsoft Visual Basic 2005)支持将默认值赋给参数。 例如,下面是一个合法的 Visual Basic 2005 声明,此声明将默认值赋给两个参数。
Public Sub MyMethod (a as Integer, _ Optional b as Double = 1.2, _ Optional c as Integer = 1)
您可以使用参数特性来分配默认的参数值。
在 Visual Basic 和 C++ 中,在调用该方法时可省略可选参数。 在 C# 中,必须为可选参数指定值。
例如,以下所有 Visual Basic 和 C++ 示例都是对 MyMethod 的有效调用。
MyMethod(10, 55.3, 12); MyMethod(10, 1.3); // c == 1 MyMethod(11); // b == 1.2, c == 1
若要使用反射检索实参的默认值,请获取形参的 ParameterInfo 对象,然后使用 ParameterInfo.DefaultValue 属性检索默认值。 如果不存在默认值,该属性将返回 Value.DBNull。
下面的示例向控制台显示 MyMethod 的默认值。
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 2005 示例演示如何使用反射调用具有默认参数的方法。
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 的对象数组。
访问自定义特性
当特性与程序元素相关联后,可以使用反射来查询它们是否存在以及它们的值。 在 .NET Framework 1.0 和 1.1 版中,是在执行上下文中检查自定义特性。 .NET Framework 2.0 版提供了新的加载上下文(只反射上下文),该上下文可用于检查无法加载执行的代码。
只反射上下文
加载到只反射上下文中的代码无法执行。 这意味着不能创建自定义特性的实例,因为这将需要执行其构造函数。 若要在只反射上下文中加载和检查自定义特性,请使用 CustomAttributeData 类。 可以通过使用静态 CustomAttributeData.GetCustomAttributes 方法的相应重载获取此类的实例。 请参见如何:将程序集加载到仅反射上下文中。
执行上下文
用于查询执行上下文中的特性的主要反射方法是 MemberInfo.GetCustomAttributes 和 Attribute.GetCustomAttributes。
自定义特性的可访问性根据附加该特性的程序集来进行检查。 这相当于检查附加自定义特性的程序集中,其类型的方法是否可以调用自定义特性的构造函数。
诸如 Assembly.GetCustomAttributes(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())相同。 为了防止客户端发现关于用户定义的自定义特性类型的信息,请将该类型的成员定义为非公共成员。
下面的示例说明使用反射访问自定义特性的基本方法。
using System; public class ExampleAttribute : Attribute { private string stringVal; public ExampleAttribute() { stringVal = "This is the default string."; } public string StringValue { get { return stringVal; } set { stringVal = value; } } } [Example(StringValue="This is a string.")] class Class1 { public static void Main() { System.Reflection.MemberInfo info = typeof(Class1); foreach (object attrib in info.GetCustomAttributes(true)) { Console.WriteLine(attrib); } } }
指定完全限定的类型名称
要为各种反射操作提供有效的输入,必须指定类型名称。 完全限定的类型名称包含程序集名称说明、命名空间说明和类型名称。 类型名称说明由 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 |
指定特殊字符
在类型名称中,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 不区分大小写。
属性名 |
说明 |
允许值 |
---|---|---|
版本 |
程序集版本号 |
Major.Minor.Build.Revision,其中 Major、Minor、Build 和 Revision 是 0 和 65535 之间(含 0 和 65535)的整数。 |
PublicKey |
完全公钥 |
完全公钥的十六进制字符串值。 指定 null 引用(在 Visual Basic 中为 Nothing)以显式指示私有程序集。 |
PublicKeyToken |
公钥标记(完全公钥的 8 字节哈希) |
公钥标记的十六进制字符串值。 指定 null 引用(在 Visual Basic 中为 Nothing)以显式指示私有程序集。 |
Culture |
程序集区域性 |
程序集的 RFC-1766 格式区域性,或者对于独立于语言(非附属)的程序集为“非特定”。 |
自定义 |
自定义的二进制大对象 (BLOB)。 它当前仅用于由本机图像生成器 (Ngen) 生成的程序集。 |
自定义的字符串,由本机图像生成器工具用来向程序集缓存通知所安装的程序集为本机图像,因此将安装在本机图像缓存中。 也称作 Zap 字符串。 |
下面的示例演示具有默认区域性的简单命名程序集的 AssemblyName。
com.microsoft.crypto, Culture=""
下面的示例演示区域性为“en”的强命名程序集的完全限定引用。
com.microsoft.crypto, Culture=en, PublicKeyToken=a5d015c7d5a0b012, Version=1.0.0.0
下面的示例演示部分指定的 AssemblyName,它可以由具有强名称或简单名称的程序集来满足。
com.microsoft.crypto
com.microsoft.crypto, Culture=""
com.microsoft.crypto, Culture=en
下面的示例都演示一个部分指定的 AssemblyName,它必须由具有简单名称的程序集来满足。
com.microsoft.crypto, Culture="", PublicKeyToken=null com.microsoft.crypto, Culture=en, PublicKeyToken=null
下面的示例都显示一个部分指定的 AssemblyName,它必须由具有强名称的程序集来满足。
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.CreateType 的 TypeBuilder 对象。 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 的单维数组。
非托管反射 API
多数元数据都可使用托管反射来访问。 少数情况下,使用托管反射中提供的类很难检索元数据。 也有时托管反射并不能提供最佳性能。
有关非托管反射 API 的信息,请参见 元数据接口。 元数据结构 和元数据枚举中记录了该 API 使用的结构和枚举。
注意 |
---|
使用非托管反射 API 需要熟悉 Common Language Infrastructure (CLI) 文档,尤其是“Partition II: Metadata Definition and Semantics”(第二部分:元数据定义和语义)和“Partition III: CIL Instruction Set”(第三部分:CIL 指令集)。 该文档可联机获得;请参见 MSDN 上的 ECMA C# and Common Language Infrastructure Standards(ECMA C# 和公共语言基础结构标准)和 Ecma International 网站上的 Standard ECMA-335 - Common Language Infrastructure (CLI)(标准 ECMA-335 - 公共语言基础结构 (CLI))。 |
如何:使用反射将委托挂钩
当使用反射来加载和运行程序集时,不能使用 C# += 运算符或 Visual Basic AddHandler 语句等语言功能将事件挂钩。 以下过程显示如何通过用反射获取所需的全部类型将现有方法挂钩到事件,以及如何使用反射发出来创建动态方法并将其挂钩到事件。
注意 |
---|
有关事件处理委托的其他挂钩方式,请参见 EventInfo 类的 AddEventHandler 方法的代码示例。 |
使用反射挂钩委托
-
加载包含引发事件的类型的程序集。 程序集通常使用 Assembly.Load 方法加载。 为了使此示例尽量简单,在当前程序集中使用了派生窗体,所以使用 GetExecutingAssembly 方法来加载当前程序集。
Assembly assem = Assembly.GetExecutingAssembly();
-
获取表示类型的 Type 对象,并创建一个该类型的实例。 由于窗体具有默认构造函数,所以在下面的代码中使用了 CreateInstance(Type) 方法。 如果您创建的类型没有默认构造函数,CreateInstance 方法还有其他几种重载可供使用。 新实例存储为类型 Object,以保持对程序集一无所知的状态。 (通过反射可以获取程序集中的类型,而无需事先了解其名称。)
Type tExForm = assem.GetType("ExampleForm"); Object exFormAsObj = Activator.CreateInstance(tExForm);
-
获取表示该事件的 EventInfo 对象,并使用 EventHandlerType 属性来获取用于处理事件的委托类型。 在下面的代码中,获取了 Click 事件的 EventInfo。
EventInfo evClick = tExForm.GetEvent("Click"); Type tDelegate = evClick.EventHandlerType;
-
获取表示处理事件的方法的 MethodInfo 对象。 本主题后面示例部分中的完整程序代码包含一个与 EventHandler 委托的签名匹配的方法,该方法处理 Click 事件,但您也可以在运行时生成动态方法。 有关详细信息,请参见附带的使用动态方法在运行时生成事件处理程序过程。
MethodInfo miHandler = typeof(Example).GetMethod("LuckyHandler", BindingFlags.NonPublic | BindingFlags.Instance);
-
使用 CreateDelegate 方法创建委托的实例。 此方法是静态的(在 Visual Basic 中为 Shared),所以必须提供委托类型。 建议使用带有 MethodInfo 的 CreateDelegate 重载。
Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler);
-
获取 add 访问器方法,并调用该方法以将事件挂钩。 所有事件都具有一个 add 访问器或 remove 访问器,这些访问器被高级语言的语法隐藏。 例如,C# 使用 += 运算符将事件挂钩,而 Visual Basic 则使用 AddHandler 语句。 下面的代码获取 Click 事件的 add 访问器并以后期绑定方式调用它,并在委托实例中传递。 参数必须作为数组传递。
MethodInfo addHandler = evClick.GetAddMethod(); Object[] addHandlerArgs = { d }; addHandler.Invoke(exFormAsObj, addHandlerArgs);
-
测试事件。 下面的代码显示了在代码示例中定义的窗体。 单击该窗体将调用事件处理程序。
Application.Run((Form) exFormAsObj);
使用动态方法在运行时生成事件处理程序
-
使用轻量动态方法和反射发出可在运行时生成事件处理程序方法。 若要构造事件处理程序,您需要知道返回类型和委托的参数类型。 可以通过检查委托的 Invoke 方法来获取这些类型。 下面的代码使用 GetDelegateReturnType 和 GetDelegateParameterTypes 方法获取此信息。 在本主题后面的示例部分中可以找到这些方法的代码。
不需要命名 DynamicMethod,所以可以使用空字符串。 在下面的代码中,最后一个参数将动态方法与当前类型相关联,从而允许委托访问 Example 类的所有公共和私有成员。
Type returnType = GetDelegateReturnType(tDelegate); if (returnType != typeof(void)) throw new ApplicationException("Delegate has a return type."); DynamicMethod handler = new DynamicMethod("", null, GetDelegateParameterTypes(tDelegate), typeof(Example));
-
生成方法体。 此方法加载字符串、调用带有字符串的 MessageBox.Show 方法重载、从堆栈弹出返回值(因为处理程序没有返回类型)并返回这些值。 若要了解有关发出动态方法的更多信息,请参见如何:定义和执行动态方法。
ILGenerator ilgen = handler.GetILGenerator(); Type[] showParameters = { typeof(String) }; MethodInfo simpleShow = typeof(MessageBox).GetMethod("Show", showParameters); ilgen.Emit(OpCodes.Ldstr, "This event handler was constructed at run time."); ilgen.Emit(OpCodes.Call, simpleShow); ilgen.Emit(OpCodes.Pop); ilgen.Emit(OpCodes.Ret);
-
通过调用动态方法的 CreateDelegate 方法完成动态方法。 使用 add 访问器向事件的调用列表中添加委托。
Delegate dEmitted = handler.CreateDelegate(tDelegate); addHandler.Invoke(exFormAsObj, new Object[] { dEmitted });
-
测试事件。 下面的代码将加载在代码示例中定义的窗体。 单击该窗体将同时调用预定义的事件处理程序和发出的事件处理程序。
Application.Run((Form) exFormAsObj);
示例
下面的代码示例显示如何使用反射将现有方法挂钩到事件,以及如何使用 DynamicMethod 类在运行时发出方法并将其挂钩到事件。
using System; using System.Reflection; using System.Reflection.Emit; using System.Windows.Forms; class ExampleForm : Form { public ExampleForm() : base() { this.Text = "Click me"; } } class Example { public static void Main() { Example ex = new Example(); ex.HookUpDelegate(); } private void HookUpDelegate() { // Load an assembly, for example using the Assembly.Load // method. In this case, the executing assembly is loaded, to // keep the demonstration simple. // Assembly assem = Assembly.GetExecutingAssembly(); // Get the type that is to be loaded, and create an instance // of it. Activator.CreateInstance has other overloads, if // the type lacks a default constructor. The new instance // is stored as type Object, to maintain the fiction that // nothing is known about the assembly. (Note that you can // get the types in an assembly without knowing their names // in advance.) // Type tExForm = assem.GetType("ExampleForm"); Object exFormAsObj = Activator.CreateInstance(tExForm); // Get an EventInfo representing the Click event, and get the // type of delegate that handles the event. // EventInfo evClick = tExForm.GetEvent("Click"); Type tDelegate = evClick.EventHandlerType; // If you already have a method with the correct signature, // you can simply get a MethodInfo for it. // MethodInfo miHandler = typeof(Example).GetMethod("LuckyHandler", BindingFlags.NonPublic | BindingFlags.Instance); // Create an instance of the delegate. Using the overloads // of CreateDelegate that take MethodInfo is recommended. // Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler); // Get the "add" accessor of the event and invoke it late- // bound, passing in the delegate instance. This is equivalent // to using the += operator in C#, or AddHandler in Visual // Basic. The instance on which the "add" accessor is invoked // is the form; the arguments must be passed as an array. // MethodInfo addHandler = evClick.GetAddMethod(); Object[] addHandlerArgs = { d }; addHandler.Invoke(exFormAsObj, addHandlerArgs); // Event handler methods can also be generated at run time, // using lightweight dynamic methods and Reflection.Emit. // To construct an event handler, you need the return type // and parameter types of the delegate. These can be obtained // by examining the delegate's Invoke method. // // It is not necessary to name dynamic methods, so the empty // string can be used. The last argument associates the // dynamic method with the current type, giving the delegate // access to all the public and private members of Example, // as if it were an instance method. // Type returnType = GetDelegateReturnType(tDelegate); if (returnType != typeof(void)) throw new ApplicationException("Delegate has a return type."); DynamicMethod handler = new DynamicMethod("", null, GetDelegateParameterTypes(tDelegate), typeof(Example)); // Generate a method body. This method loads a string, calls // the Show method overload that takes a string, pops the // return value off the stack (because the handler has no // return type), and returns. // ILGenerator ilgen = handler.GetILGenerator(); Type[] showParameters = { typeof(String) }; MethodInfo simpleShow = typeof(MessageBox).GetMethod("Show", showParameters); ilgen.Emit(OpCodes.Ldstr, "This event handler was constructed at run time."); ilgen.Emit(OpCodes.Call, simpleShow); ilgen.Emit(OpCodes.Pop); ilgen.Emit(OpCodes.Ret); // Complete the dynamic method by calling its CreateDelegate // method. Use the "add" accessor to add the delegate to // the invocation list for the event. // Delegate dEmitted = handler.CreateDelegate(tDelegate); addHandler.Invoke(exFormAsObj, new Object[] { dEmitted }); // Show the form. Clicking on the form causes the two // delegates to be invoked. // Application.Run((Form) exFormAsObj); } private void LuckyHandler(Object sender, EventArgs e) { MessageBox.Show("This event handler just happened to be lying around."); } private Type[] GetDelegateParameterTypes(Type d) { if (d.BaseType != typeof(MulticastDelegate)) throw new ApplicationException("Not a delegate."); MethodInfo invoke = d.GetMethod("Invoke"); if (invoke == null) throw new ApplicationException("Not a delegate."); ParameterInfo[] parameters = invoke.GetParameters(); Type[] typeParameters = new Type[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { typeParameters[i] = parameters[i].ParameterType; } return typeParameters; } private Type GetDelegateReturnType(Type d) { if (d.BaseType != typeof(MulticastDelegate)) throw new ApplicationException("Not a delegate."); MethodInfo invoke = d.GetMethod("Invoke"); if (invoke == null) throw new ApplicationException("Not a delegate."); return invoke.ReturnType; } }
编译代码
-
代码包含编译所需的 C# using 语句(在 Visual Basic 中为 Imports)。
-
从命令行编译时,不再需要其他的程序集引用。 由于此示例是一个控制台应用程序,所以在 Visual Studio 中必须添加对 System.Windows.Forms.dll 的引用。
-
在命令行使用 csc.exe、vbc.exe 或 cl.exe 编译代码。 若要在 Visual Studio 中编译代码,请将代码置于控制台应用程序项目模板中。