CLR via C#, 4th -- 【核心机制】 -- 第23章程序集加载和反射
宿主不能基于一些具体的加载项来构建和测试,因为加载项由不同公司创建,而且极有可能是在宿主应用程序发布之后才创建的。这是宿主为什么要在运行时发现加载项的原因。
23.1 程序集加载
Load
在内部,CLR使用System.Reflection.Assembly类的静态Load方法尝试加载这个程序集。
public class Assembly { public static Assembly Load(AssemblyName assemblyRef); public static Assembly Load(String assemblyString); // Less commonly used overloads of Load are not shown }
在大多数动态可扩展应用程序中,Assembly的Load方法是将程序集加载到AppDomain的首选方式。但它要求事先掌握构成程序集标识的各个部分。开发人员经常需要写一些工具或实用程序(例如1ILDasm.exe,PEVerify.exe,CorFlags.exe,GACUtil.exe,SGen.exe,SN.exe和xSD.exe等)来操作程序集,它们都要获取引用了程序集文件路径名(包括文件扩展名)的命令行实参。
LoadFrom
调用Assembly的LoadFrom方法加载指定了路径名的程序集:
public class Assembly { public static Assembly LoadFrom(String path); // Less commonly used overloads of LoadFrom are not shown }
在内部,LoadFrom首先调用System.Reflection.AssemblyName类的静态GetAssemblyName方法。该方法打开指定的文件,找到AssemblyRef元数据表的记录项,提取程序集标识信息,然后以一个System.Reflection.AssemblyName对象的形式返回这些信息(文件同时会关闭)。随后,LoadFrom方法在内部调用Assembly的Load方法,将AssemblyName对象传给它。
如果传递的是一个Internet位置,CLR会下载文件,把它安装到用户的下载缓存中,再从那儿加载文件。注意,当前必须联网,否则会抛出异常。但如果文件之前已下载过,而且Microsoft Internet Explorer被设为脱机工作(在Internet Explorer中单击“文件"1"脱机工作”),就会使用以前下载的文件,不会抛出异常。
LoadFile
Microsoft Visual Studio的UI设计人员和其他工具一般用的是Assembly的LoadFile方法。这个方法可从任意路径加载程序集,而且可以将具有相同标识的程序集多次加载到一个AppDomain中。在设计器/工具中对应用程序的UI进行了修改,而且用户重新生成了程序集时,便有可能发生这种情况。
ReflectionOnlyLoadFrom / ReflectionOnlyLoad
如果只想通过反射来分析程序集的元数据,并希望确保程序集中的任何代码都不会执行,那么加载程序集的最佳方式就是使用Assembly的ReflectionOnlyLoadFrom方法或者使用Assembly的ReflectionOnlyLoad方法。
public class Assembly { public static Assembly ReflectionOnlyLoadFrom(String assemblyFile); public static Assembly ReflectionOnlyLoad(String assemblyString); // Less commonly used overload of ReflectionOnlyLoad is not shown }
CLR不提供卸载单独程序集的能力。如果CLR允许这样做,那么一旦线程从某个方法返回至已卸载的一个程序集中的代码,应用程序就会崩溃。
只部署一个EXE文件
对于添加的每个DLL,都显示它的属性,将它的“生成操作”更改为“嵌入的资源”。(要注意这会增大应用程序在运行时的内存消耗。)
23.2 使用反射构建动态可扩展应用程序
在运行时,当应用程序需要从特定程序集中加载特定类型以执行特定任务时,也要使用反射。例如,应用程序可要求用户提供程序集和类型名。然后,应用程序可显式加载程序集,构造类型的实例,再调用类型中定义的方法。
23.3 反射的性能
反射的两个缺点
- 反射造成编译时无法保证类型安全性。由于反射严重依赖字符串,所以会丧失编译时的类型安全性。例如,执行Type.GetType("int");要求通过反射在程序集中查找名为"int"的类型,代码会通过编译,但在运行时会返回null,因为CLR只知"System.Int32",不知"int"。
- 反射速度慢。使用反射时,类型及其成员的名称在编译时未知;你要用字符串名称标识每个类型及其成员,然后在运行时发现它们。也就是说,使用System.Reflection命名空间中的类型扫描程序集的元数据时,反射机制会不停地执行字符串搜索。通常,字符串搜索执行的是不区分大小写的比较,这会进一步影响速度。
基于上述所有原因,最好避免利用反射来访问字段或调用方法/属性。应该利用以下两种技术之一开发应用程序来动态发现和构造类型实例。
- 让类型从编译时已知的基类型派生。在运行时构造派生类型的实例,将对它的引用放到基类型的变量中(利用转型),再调用基类型定义的虚方法。
- 让类型实现编译时已知的接口。在运行时构造类型的实例,将对它的引用放到接口类型的变量中(利用转型),再调用接口定义的方法。
23.3.1 发现程序集中定义的类型
最常用的API是Assembly的ExportedTypes属性
using System; using System.Reflection; public static class Program { public static void Main() { String dataAssembly = "System.Data, version=4.0.0.0, " + "culture=neutral, PublicKeyToken=b77a5c561934e089"; LoadAssemAndShowPublicTypes(dataAssembly); } private static void LoadAssemAndShowPublicTypes(String assemId) { // Explicitly load an assembly in to this AppDomain Assembly a = Assembly.Load(assemId); // Execute this loop once for each Type // publiclyexported from the loaded assembly foreach (Type t in a.ExportedTypes) { // Display the full name of the type Console.WriteLine(t.FullName); } } }
23.3.2 类型对象的准确含义
System.Object的方法GetType,调用这个方法时,CLR会判断指定对象的类型,并返回对该类型的Type对象的引用。由于在一个AppDomain中,每个类型只有一个Type对象,所以可以使用相等和不等操作符来判断两个对象是不是相同的类型:
private static Boolean AreObjectsTheSameType(Object o1, Object o2) { return o1.GetType() == o2.GetType(); }
除了调用Object的GetType方法,FCL还提供了获得Type对象的其他几种方式。
- System.Type类型提供了静态GetType方法的几个重载版本。所有版本都接受一个String参数。字符串必须指定类型的全名(包括它的命名空间)。注意不允许使用编译器支持的基元类型(比如C#的int,string,bool等),这些名称对于CLR没有任何意义。
- System.Type类型提供了静态ReflectionOnlyGetType方法。该方法与上一条提到的GetType方法在行为上相似,只是类型会以“仅反射”的方式加载,不能执行。
- System.TypeInfo类型提供了实例成员DeclaredNestedTypes和GetDeclaredNestedType.
- System.Reflection.Assembly类型提供了实例成员GetType,DefinedTypes和ExportedTypes
巴克斯-诺尔范式(Backus-Naur Form,BNF)
typeof
许多编程语言都允许使用一个操作符并根据编译时已知的类型名称来获得Type对象。尽量用这个操作符获得Type引用,而不要使用上述列表中的任何方法,因为操作符生成的代码通常更快。C#的这个操作符称为typeof,通常用它将晚期绑定的类型信息与早期绑定(编译时已知)的类型信息进行比较。
private static void SomeMethod(Object o) { // GetType returns the type of the object at runtime (late bound) // typeof returns the type of the specified class (earlybound) if (o.GetType() == typeof(FileInfo)) { ... } if (o.GetType() == typeof(DirectoryInfo)) { ... } }
注意 上述代码的第一个if语句检查变量。是否引用了Filelnfo类型的对象;它不检查。是否引用从Filelnfo类型派生的对象。换言之,上述代码测试的是精确匹配,而非兼容匹配。(使用转型或C#的is/as操作符时,测试的就是兼容匹配。
GetTypelnfo
如前所述,Type对象是轻量级的对象引用。要更多地了解类型本身,必须获取一个Typelnfo对象,后者才代表类型定义。可调用System.Reflection.ntrospectionExtensions的GetTypelnfo扩展方法将Type对象转换成Typelnfo对象。
Type typeReference = ...; // For example: o.GetType() or typeof(Object) TypeInfo typeDefinition = typeReference.GetTypeInfo();
AsType
另外,虽然作用不大,但还可调用Typelnfo的AsType方法将Typelnfo对象转换为Type对象。
TypeInfo typeDefinition = ...;
Type typeReference = typeDefinition.AsType();
23.3.3构建Exception派生类型的层次结构
以下代码使用本章讨论的许多概念将一组程序集加载到AppDomain中,并显示最终从System.Exception派生的所有类。顺便说一句,20.4节"FCL定义的异常类”展示的Exception层次结构就是用这个程序显示的。

public static void Go() { // Explicitly load the assemblies that we want to reflect over LoadAssemblies(); // Filter & sort all the types var allTypes = (from a in AppDomain.CurrentDomain.GetAssemblies() from t in a.ExportedTypes where typeof(Exception).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()) orderby t.Name select t).ToArray(); // Build the inheritance hierarchy tree and show it Console.WriteLine(WalkInheritanceHierarchy(new StringBuilder(), 0, typeof(Exception), allTypes)); } private static StringBuilder WalkInheritanceHierarchy( StringBuilder sb, Int32 indent, Type baseType, IEnumerable<Type> allTypes) { String spaces = new String(' ', indent * 3); sb.AppendLine(spaces + baseType.FullName); foreach (var t in allTypes) { if (t.GetTypeInfo().BaseType != baseType) continue; WalkInheritanceHierarchy(sb, indent + 1, t, allTypes); } return sb; } private static void LoadAssemblies() { String[] assemblies = { "System, PublicKeyToken={0}", "System.Core, PublicKeyToken={0}", "System.Data, PublicKeyToken={0}", "System.Design, PublicKeyToken={1}", "System.DirectoryServices, PublicKeyToken={1}", "System.Drawing, PublicKeyToken={1}", "System.Drawing.Design, PublicKeyToken={1}", "System.Management, PublicKeyToken={1}", "System.Messaging, PublicKeyToken={1}", "System.Runtime.Remoting, PublicKeyToken={0}", "System.Security, PublicKeyToken={1}", "System.ServiceProcess, PublicKeyToken={1}", "System.Web, PublicKeyToken={1}", "System.Web.RegularExpressions, PublicKeyToken={1}", "System.Web.Services, PublicKeyToken={1}", "System.Xml, PublicKeyToken={0}", }; String EcmaPublicKeyToken = "b77a5c561934e089"; String MSPublicKeyToken = "b03f5f7f11d50a3a"; // Get the version of the assembly containing System.Object // We'll assume the same version for all the other assemblies Version version = typeof(System.Object).Assembly.GetName().Version; // Explicitly load the assemblies that we want to reflect over foreach (String a in assemblies) { String AssemblyIdentity = String.Format(a, EcmaPublicKeyToken, MSPublicKeyToken) + ", Culture=neutral, Version=" + version; Assembly.Load(AssemblyIdentity); } }
23.3.4 构造类型的实例
FCL提供了以下几个机制构造类型的实例:
- System.Activator的Createlnstance方法
- System.Activator的CreatelnstanceFrom方法
- System.AppDomain的方法(CreateInstance, CreateInstanceAndUnwrap, CreateInstanceFrom , CreateInstanceFromAndUnwrap.)
- System.Reflection.Constructorlnfo的Invoke实例方法
利用前面列出的机制,可为除数组(System.Array派生类型)和委托(System.MulticastDelegate派生类型)之外的所有类型创建对象。创建数组需要调用Array的静态Createlnstance方法(有几个重载的版本)。所有版本的Createlnstance方法获取的第一个参数都是对数组元素Type的引用。CreateInstance的其他参数允许指定数组维数和上下限的各种组合。创建委托则要调用MethodInfo的静态CreateDelegate方法。所有版本的CreateDelegate方法获取的第一个参数都是对委托Type的引用。CreateDelegate方法的其他参数允许指定在调用实例方法时应将哪个对象作为this参数传递。
构造泛型类型的实例首先要获取对开放类型的引用,然后调用Type的MakeGenericType方法并向其传递一个数组(其中包含要作为类型实参使用的类型)"。然后,获取返回的Type对象并把它传给上面列出的某个方法。下面是一个例子:
using System; using System.Reflection; internal sealed class Dictionary<TKey, TValue> { } public static class Program { public static void Main() { // Get a reference to the generic type's type object Type openType = typeof(Dictionary<,>); // Close the generic type by using TKey=String, TValue=Int32 Type closedType = openType.MakeGenericType(typeof(String), typeof(Int32)); // Construct an instance of the closed type Object o = Activator.CreateInstance(closedType); // Prove it worked Console.WriteLine(o.GetType()); } }
23.4 设计支持加载项(Add-Ins)的应用程序
- 创建“宿主SDK"(HostSDK.dll)程序集,它定义了一个接口,接口的方法作为宿主应用程序与加载项之间的通信机制使用。
- 在加载项程序集中定义自己的类型(AddInTypes.dll)。(这些程序集将引用你的“宿主”程序集中的类型。)
- 创建单独的“宿主应用程序”程序集(Host.exe),在其中包含你的应用程序的类型。(这个显然要引用“宿主SDK"程序集,并使用其中定义的类型。)
HostSDK.dll
using System; namespace Wintellect.HostSDK { public interface IAddIn { String DoSomething(Int32 x); } }
AddInTypes.dll
using System; using Wintellect.HostSDK; public sealed class AddIn_A : IAddIn { public AddIn_A() { } public String DoSomething(Int32 x) { return "AddIn_A: " + x.ToString(); } } public sealed class AddIn_B : IAddIn { public AddIn_B() { } public String DoSomething(Int32 x) { return "AddIn_B: " + (x * 2).ToString(); } }
Host.exe
using System; using System.IO; using System.Reflection; using System.Collections.Generic; using Wintellect.HostSDK; public static class Program { public static void Main() { // Find the directory that contains the Host exe String AddInDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); // Assume AddIn assemblies are in same directory as host's EXE file var AddInAssemblies = Directory.EnumerateFiles(AddInDir, "*.dll"); // Create a collection of AddIn Types usable by the host var AddInTypes = from file in AddInAssemblies let assembly = Assembly.Load(file) from t in assembly.ExportedTypes // Publicly exported types // Type is usable if it is a class that implements IAddIn where t.IsClass && typeof(IAddIn).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()) select t; // Initialization complete: the host has discovered the usable AddIns // Here's how the host can construct Add In objects and use them foreach (Type t in AddInTypes) { IAddIn ai = (IAddIn) Activator.CreateInstance(t); Console.WriteLine(ai.DoSomething(5)); } } }
“托管可扩展性框架"(Managed Extensibility Framework,MEF)
这个简单的宿主/加载项例子没有用到AppDomain,但在实际应用中,每个加载项都可能要在自己的AppDomain中创建,每个AppDomain都有自己的安全性和配置设置。当然,如果希望将加载项从内存中移除,可以卸载相应的AppDomain。为了跨AppDomain边界通信,可告诉加载项开发人员从MashalByRefObject派生出他们自己的加载类型。但另一个更常见的办法是让宿主应用程序定义自己的、从MashalByRefObject派生的内部类型。每个AppDomain创建好后,宿主要在新AppDomain中创建它自己的MashalByRefObject派生类型实例。宿主的代码(位于默认AppDomain中)将与它自己的类型(位于其他AppDomain中)通信,让后者载入加载项程序集,并创建和使用加载的类型的实例。
23.5 使用反射发现类型的成员
23.5.1 发现类型的成员
System.Reflection.Memberlnfo

using System; using System.Reflection; public static class Program { public static void Main() { // Loop through all assemblies loaded in this AppDomain Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly a in assemblies) { Show(0, "Assembly: {0}", a); // Find Types in the assembly foreach (Type t in a.ExportedTypes) { Show(1, "Type: {0}", t); // Discover the type's members foreach (MemberInfo mi in t.GetTypeInfo().DeclaredMembers) { String typeName = String.Empty; if (mi is Type) typeName = "(Nested) Type"; if (mi is FieldInfo) typeName = "FieldInfo"; if (mi is MethodInfo) typeName = "MethodInfo"; if (mi is ConstructorInfo) typeName = "ConstructoInfo"; if (mi is PropertyInfo) typeName = "PropertyInfo"; if (mi is EventInfo) typeName = "EventInfo"; Show(2, "{0}: {1}", typeName, mi); } } } } private static void Show(Int32 indent, String format, params Object[] args) { Console.WriteLine(new String(' ', 3 * indent) + format, args); } }
TABLE 23-1 Properties and Methods Common to All MemberInfo-Derived Types
Member Name |
Member Type |
Description |
Name |
String property |
Returns the name of the member. |
DeclaringType |
Type property |
Returns the Type that declares the member. |
Module |
Module property |
Returns the Module that declares the member. |
CustomAttributes |
Property returning IEnumerable<Custom AttributeData> |
Returns a collection in which each element identi -fies an instance of a custom attribute applied to this member. Custom attributes can be applied to any member. Even though Assembly does not derive from MemberInfo, it provides the same property that can be used with assemblies. |
23.5.2 调用类型的成员
TABLE 23-2 How to Invoke a Member
Type of Member |
Method to Invoke Member |
FieldInfo |
Call GetValue to get a field’s value. Call SetValue to set a field’s value. |
ConstructorInfo |
Call Invoke to construct an instance of the type and call a constructor. |
MethodInfo |
Call Invoke to call a method of the type. |
PropertyInfo |
Call GetValue to call a property’s get accessor method. Call SetValue to call a property’s set accessor method. |
EventInfo |
Call AddEventHandler to call an event’s add accessor method. Call RemoveEventHandler to call an event’s remove accessor method. |
Propertylnfo类型代表与属性有关的元数据信息(参见第10章“属性”);也就是说,Propertylnfo提供了CanRead,CanWrite和PropertyType只读属性,它们指出属性是否可读和可写,以及属性的数据类型是什么。PropertyInfo还提供了只读GetMethod和SetMethod属性,它们返回代表属性get和set访问器方法的MethodInfo对象。.Propertylnfo的GetValue和SetValue方法只是为了提供方便:在内部,它们会自己调用合适的MethodInfo对象。为了支持有参属性(C#的索引器),GetValue和SetValue方法提供了一个Objectl1类型的index参数。
EventInfo类型代表与事件有关的元数据信息(参见第11章“事件”),EventInfo类型提供了只读EventHandlerType属性,返回事件的基础委托的Type,Eventinfo类型还提供了只读AddMethod和RemoveMethod属性,返回为事件增删委托的方法的MethodInfo对象。增删委托可调用这些MethodInfo对象,也可调用EventiInfo类型提供的更好用的AddEventHandler和RemoveEventHandler方法。
以下示例应用程序演示了用反射来访问类型成员的各种方式:

using System; using System.Reflection; using Microsoft.CSharp.RuntimeBinder; using System.Linq; // This class is used to demonstrate reflection // It has a field, constructor, method, property, and an event internal sealed class SomeType { private Int32 m_someField; public SomeType(ref Int32 x) { x *= 2; } public override String ToString() { return m_someField.ToString(); } public Int32 SomeProp { get { return m_someField; } set { if (value < 1) throw new ArgumentOutOfRangeException("value"); m_someField = value; } public event EventHandler SomeEvent; private void NoCompilerWarnings() { SomeEvent.ToString();} } public static class Program { public static void Main() { Type t = typeof(SomeType); BindToMemberThenInvokeTheMember(t); Console.WriteLine(); BindToMemberCreateDelegateToMemberThenInvokeTheMember(t); Console.WriteLine(); UseDynamicToBindAndInvokeTheMember(t); Console.WriteLine(); } private static void BindToMemberThenInvokeTheMember(Type t) { Console.WriteLine("BindToMemberThenInvokeTheMember"); // Construct an instance Type ctorArgument = Type.GetType("System.Int32&"); // or typeof(Int32).MakeByRefType(); ConstructorInfo ctor = t.GetTypeInfo().DeclaredConstructors.First( c => c.GetParameters()[0].ParameterType == ctorArgument); Object[] args = new Object[] { 12 }; // Constructor arguments Console.WriteLine("x before constructor called: " + args[0]); Object obj = ctor.Invoke(args); Console.WriteLine("Type: " + obj.GetType()); Console.WriteLine("x after constructor returns: " + args[0]); // Read and write to a field FieldInfo fi = obj.GetType().GetTypeInfo().GetDeclaredField("m_someField"); fi.SetValue(obj, 33); Console.WriteLine("someField: " + fi.GetValue(obj)); // Call a method MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString"); String s = (String)mi.Invoke(obj, null); Console.WriteLine("ToString: " + s); // Read and write a property PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp"); try { pi.SetValue(obj, 0, null); } catch (TargetInvocationException e) { if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw; Console.WriteLine("Property set catch."); } pi.SetValue(obj, 2, null); Console.WriteLine("SomeProp: " + pi.GetValue(obj, null)); // Add and remove a delegate from the event EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent"); EventHandler eh = new EventHandler(EventCallback); // See ei.EventHandlerType ei.AddEventHandler(obj, eh); ei.RemoveEventHandler(obj, eh); } // Callback method added to the event private static void EventCallback(Object sender, EventArgs e) { } private static void BindToMemberCreateDelegateToMemberThenInvokeTheMember(Type t) { Console.WriteLine("BindToMemberCreateDelegateToMemberThenInvokeTheMember"); // Construct an instance (You can't create a delegate to a constructor) Object[] args = new Object[] { 12 }; // Constructor arguments Console.WriteLine("x before constructor called: " + args[0]); Object obj = Activator.CreateInstance(t, args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]); // NOTE: You can't create a delegate to a field // Call a method MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString"); var toString = mi.CreateDelegate<Func<String>>(obj); String s = toString(); Console.WriteLine("ToString: " + s); // Read and write a property PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp"); var setSomeProp = pi.SetMethod.CreateDelegate<Action<Int32>>(obj); try { setSomeProp(0); } catch (ArgumentOutOfRangeException) { Console.WriteLine("Property set catch."); } setSomeProp(2); var getSomeProp = pi.GetMethod.CreateDelegate<Func<Int32>>(obj); Console.WriteLine("SomeProp: " + getSomeProp()); // Add and remove a delegate from the event EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent"); var addSomeEvent = ei.AddMethod.CreateDelegate<Action<EventHandler>>(obj); addSomeEvent(EventCallback); var removeSomeEvent = ei.RemoveMethod.CreateDelegate<Action<EventHandler>>(obj); removeSomeEvent(EventCallback); } private static void UseDynamicToBindAndInvokeTheMember(Type t) { Console.WriteLine("UseDynamicToBindAndInvokeTheMember"); // Construct an instance (You can't use dynamic to call a constructor) Object[] args = new Object[] { 12 }; // Constructor arguments Console.WriteLine("x before constructor called: " + args[0]); dynamic obj = Activator.CreateInstance(t, args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]); // Read and write to a field try { obj.m_someField = 5; Int32 v = (Int32)obj.m_someField; Console.WriteLine("someField: " + v); } catch (RuntimeBinderException e) { // We get here because the field is private Console.WriteLine("Failed to access field: " + e.Message); } // Call a method String s = (String)obj.ToString(); Console.WriteLine("ToString: " + s); // Read and write a property try { obj.SomeProp = 0; } catch (ArgumentOutOfRangeException) { Console.WriteLine("Property set catch."); } obj.SomeProp = 2; Int32 val = (Int32)obj.SomeProp; Console.WriteLine("SomeProp: " + val); // Add and remove a delegate from the event obj.SomeEvent += new EventHandler(EventCallback); obj.SomeEvent = new EventHandler(EventCallback); } } internal static class ReflectionExtensions { // Helper extension method to simplify syntax to create a delegate public static TDelegate CreateDelegate<TDelegate>(this MethodInfo mi, Object target = null) { return (TDelegate)(Object)mi.CreateDelegate(typeof(TDelegate), target); } }
23.5.3 使用绑定句柄减少进程的内存消耗
许多应用程序都绑定了一组类型(Type对象)或类型成员(MemberInfo派生对象),并将这些对象保存在某种形式的集合中。以后,应用程序搜索这个集合,查找特定对象,然后调用(invoke)这个对象。这个机制很好,只是有个小问题:Type和MemberInfo派生对象需要大量内存。所以,如果应用程序容纳了太多这样的对象,但只是偶尔调用,应用程序消耗的内存就会急剧增加,对应用程序的性能产生负面影响。
CLR内部用更精简的方式表示这种信息。CLR之所以为应用程序创建这些对象,只是为了方便开发人员。CLR不需要这些大对象就能运行。如果需要保存/缓存大量Type和Memberlnfo派生对象,开发人员可以使用运行时句柄(runtime handle)代替对象以减小工作集(占用的内存),FCL定义了三个运行时句柄类型(全部都在System命名空间中),包括RuntimeTypeHandle,RuntimeFieldHandle和RuntimeMethodHandle,三个类型都是值类型,都只包含一个字段,也就是一个IntPtr;这使类型的实例显得相当精简(相当省内存)。IntPtr字段是一个句柄,引用了AppDomain的Loader堆中的一个类型、字段或方法。
using System; using System.Reflection; using System.Collections.Generic; public sealed class Program { private const BindingFlags c_bf = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; public static void Main() { // Show size of heap before doing any reflection stuff Show("Before doing anything"); // Build cache of MethodInfo objects for all methods in MSCorlib.dll List<MethodBase> methodInfos = new List<MethodBase>(); foreach (Type t in typeof(Object).Assembly.GetExportedTypes()) { // Skip over any generic types if (t.IsGenericTypeDefinition) continue; MethodBase[] mb = t.GetMethods(c_bf); methodInfos.AddRange(mb); } // Show number of methods and size of heap after binding to all methods Console.WriteLine("# of methods={0:N0}", methodInfos.Count); Show("After building cache of MethodInfo objects"); // Build cache of RuntimeMethodHandles for all MethodInfo objects List<RuntimeMethodHandle> methodHandles = methodInfos.ConvertAll<RuntimeMethodHandle>(mb => mb.MethodHandle); Show("Holding MethodInfo and RuntimeMethodHandle cache"); GC.KeepAlive(methodInfos); // Prevent cache from being GC'd early methodInfos = null; // Allow cache to be GC'd now Show("After freeing MethodInfo objects"); methodInfos = methodHandles.ConvertAll<MethodBase>( rmh=> MethodBase.GetMethodFromHandle(rmh)); Show("Size of heap after re creating MethodInfo objects"); GC.KeepAlive(methodHandles); // Prevent cache from being GC'd early GC.KeepAlive(methodInfos); // Prevent cache from being GC'd early methodHandles = null; // Allow cache to be GC'd now methodInfos = null; // Allow cache to be GC'd now Show("After freeing MethodInfos and RuntimeMethodHandles"); } }
- 要将Type对象转换为一个RuntimeTypeHandle,调用Type的静态GetTypeHandle方法并传递那个Type对象引用。
- 要将一个RuntimeTypeHandle转换为Type对象,调用Type的静态方法GetTypeFromHandle,并传递那个RuntimeTypeHandle.
- 要将FieldInfo对象转换为一个RuntimeFieldHandle,查询FieldInfo的实例只读属性FieldHandle.
- 要将一个RuntimeFieldHandle转换为FieldInfo对象,调用FieldInfo的静态方法GetFieldFromHandle.
- 要将MethodInfo对象转换为一个RuntimeMethodHandle,查询MethodInfo的实例只读属性MethodHandle.
- 要将一个RuntimeMethodHandle转换为一个MethodInfo对象,调用MethodInfo的静态方法GetMethodFromHandle
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】Flutter适配HarmonyOS 5知识地图,实战解析+高频避坑指南
【推荐】开源 Linux 服务器运维管理面板 1Panel V2 版本正式发布
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Java线程池详解:高效并发编程的核心利器
· 从“看懂世界”到“改造世界”:AI发展的四个阶段你了解了吗?
· 协程本质是函数加状态机——零基础深入浅出 C++20 协程
· 编码之道,道心破碎。
· 记一次 .NET 某发证机系统 崩溃分析
· dotnetty 新的篇章- 开源
· 设计模式:简单工厂、工厂方法与抽象工厂
· DotTrace系列:1. 理解四大经典的诊断类型(上)
· 【大数据高并发核心场景实战】 - 数据持久化之冷热分离
· 这5种规则引擎,真香!