前言
本篇討論程序集的加載及反射。主要涉及到System.Reflection.Assembly和System.Type兩個(gè)類,前者可以用于訪問指定程序集的相關(guān)信息,或把程序集加載到程序當(dāng)中,后者可以訪問任何數(shù)據(jù)類型的信息。以下,是本篇文章涉及的主要內(nèi)容。
程序集加載
本節(jié)首先介紹Assembly類,該類位于System.Reflection命名空間下,它允許訪問指定程序集的元數(shù)據(jù),也包含加載和執(zhí)行程序集的中的方法。下面將介紹幾種常用的動(dòng)態(tài)加載程序集的方式:
方法名稱 | 說明 |
---|---|
Load | 加載程序集 |
LoadFrom | 加載指定路徑的程序集 |
LoadFile | 僅加載指定路徑的程序集(不包括依賴項(xiàng)) |
ReflectionOnlyLoad | 加載程序集(不執(zhí)行任何帶代碼) |
ReflectionOnlyLoadFrom | 加載指定路徑的程序集(不執(zhí)行任何代碼) |
Assembly.Load
Assembly的Load方法有幾個(gè)重載版本,兩種最常用的重載:Load(AssemblyName)和Load(String),傳入的參數(shù)是需要加載的程序集的名稱。
創(chuàng)建控制臺(tái)應(yīng)用程序AssemblyAndReflection,向解決方案添加新項(xiàng)目AssemblyLoad,添加類ClassA、ClassB、ClassC,編譯后為AssemblyLoad.dll分配強(qiáng)名稱并注冊(cè)到GAC中。
public class ClassA { public void SayHello() { Console.WriteLine("Hello.This is ClassA"); } } public class ClassB { public void SayHello() { Console.WriteLine("Hello.This is ClassB"); } } public class ClassC { public void SayHello() { Console.WriteLine("Hello.This is ClassC"); } }
向Program.cs中添加如下代碼:
static void Main(string[] args) { string fullName = "AssemblyLoad,Version=1.0.0.0,Culture=neutral,PublicKeyToken=098608575f7409cd, processor architecture=MSIL"; //string fullName = "AssemblyLoad"; //Assembly assembly = Assembly.Load(new AssemblyName(fullName)); Assembly assembly = Assembly.Load(fullName); if (assembly != null) { foreach (var c in assembly.GetTypes()) { Console.WriteLine(c.FullName); } } Console.ReadLine(); //****************************************************OutPut**************************************************** //AssemblyLoad.ClassA //AssemblyLoad.ClassB //AssemblyLoad.ClassC //************************************************************************************************************** }
注意,Load方法的參數(shù)可以是強(qiáng)命名程序集或弱命名程序集(上述代碼中注釋掉的fullName變量)。傳入不同參數(shù)時(shí),查找程序集的方式略有不同。
Assembly.LoadFrom
Assembly的LoadFrom方法加載指定了路徑名的程序集。將AssemblyLoad.dll文件放至D:\DLL\下,修改上述代碼:
static void Main(string[] args) { Assembly assembly = Assembly.LoadFrom(@"D:\DLL\AssemblyLoad.dll"); if (assembly != null) { foreach (var c in assembly.GetTypes()) { Console.WriteLine(c.FullName); } } Console.ReadLine(); //****************************************************OutPut**************************************************** //AssemblyLoad.ClassA //AssemblyLoad.ClassB //AssemblyLoad.ClassC //************************************************************************************************************** }
LoadFrom的執(zhí)行原理:
調(diào)用System.Reflection.AssemblyName類的靜態(tài)方法GetAssemblyName方法,打開指定路徑下的文件,返回AssemblyName對(duì)象。下面是GetAssemblyName方法的源碼:
[System.Security.SecuritySafeCritical] // auto-generated[ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]static public AssemblyName GetAssemblyName(String assemblyFile) { if(assemblyFile == null) throw new ArgumentNullException("assemblyFile"); Contract.EndContractBlock(); // Assembly.GetNameInternal() will not demand path discovery // permission, so do that first. String fullPath = Path.GetFullPathInternal(assemblyFile); new FileIOPermission( FileIOPermissionAccess.PathDiscovery, fullPath ).Demand(); return nGetFileInformation(fullPath); }
調(diào)用Assembly.Load方法,將步驟1中返回的AssemblyName對(duì)象作為參數(shù)傳入。
Assembly.LoadFile
加載指定路徑上的程序集文件的內(nèi)容。LoadFile方法不會(huì)加載目標(biāo)程序集引用和依賴的其他程序集。
Assembly.ReflectionOnlyLoad和Assembly.ReflectionOnlyLoadFrom
如果只希望通過反射來分析程序集的元數(shù)據(jù),并確保程序集中的任何代碼都不會(huì)被執(zhí)行,這種情況下可以使用Assembly類的ReflectionOnlyLoadFrom方法或ReflectionOnlyLoad方法。
獲取類型的信息
本節(jié)介紹System.Type類,通過這個(gè)類可以訪問任何數(shù)據(jù)類型的信息,System.Type類型是執(zhí)行類型和對(duì)象操作的起點(diǎn)。獲取Type對(duì)象的幾種方式:
Object.GetType()
int x = 100; Type t = x.GetType(); Console.WriteLine(t.FullName);
System.Type類提供的靜態(tài)方法ReflectionOnlyGetType()
string typeName = Type.ReflectionOnlyGetType("AssemblyLoad.ClassA, AssemblyLoad, Version=1.0.0.0, Culture=neutral, PublicKeyToken=098608575f7409cd, processor architecture=MSIL", false, true).FullName; Console.WriteLine(typeName);
System.Reflection.Assembly類提供的實(shí)例成員GetTypes、DefinedTypes和ExportedTypes
string fullName = "AssemblyLoad,Version=1.0.0.0,Culture=neutral,PublicKeyToken=098608575f7409cd, processor architecture=MSIL"; Assembly assembly = Assembly.Load(fullName); Console.WriteLine("assembly.GetTypes():"); foreach (var t in assembly.GetTypes()) { Console.WriteLine(t.FullName); } Console.WriteLine(); Console.WriteLine("assembly.ExportedTypes:"); foreach (var t in assembly.ExportedTypes) { Console.WriteLine(t.FullName); } Console.WriteLine(); Console.WriteLine("assembly.DefinedTypes:"); foreach (var t in assembly.DefinedTypes) { Console.WriteLine(t.FullName); } Console.WriteLine();
typeof關(guān)鍵字(應(yīng)盡量使用這個(gè)操作符來獲取Type引用,因?yàn)椴僮鞣傻拇a通常更快)
Console.WriteLine(typeof(int).FullName);
構(gòu)造類型的實(shí)例
獲取對(duì)Type派生對(duì)象的引用之后,就可以構(gòu)造該類型的實(shí)例了。
System.Activator.CreateInstance
string fullName = "AssemblyLoad,Version=1.0.0.0,Culture=neutral,PublicKeyToken=098608575f7409cd, processor architecture=MSIL"; Type t = Assembly.Load(fullName).GetType("AssemblyLoad.ClassA"); var o = Activator.CreateInstance(t); Console.WriteLine(o.GetType());
設(shè)計(jì)支持加載項(xiàng)的應(yīng)用程序
反射的性能
反射是相當(dāng)強(qiáng)大的機(jī)制,允許在運(yùn)行時(shí)發(fā)現(xiàn)并使用編譯時(shí)還不太了解的類型及成員。但是,反射也存在如下缺點(diǎn):
反射造成編譯時(shí)無法保證類型安全
反射速度慢。
基于上述原因,應(yīng)盡量避免使用反射來訪問字段或調(diào)用方法及屬性。在設(shè)計(jì)支持加載項(xiàng)的應(yīng)用程序時(shí),讓類型實(shí)現(xiàn)編譯時(shí)已知的接口,在運(yùn)行時(shí)構(gòu)造類型的實(shí)例,將對(duì)它的引用放到接口類型的變量中,再調(diào)用接口定義的方法。
創(chuàng)建支持加載項(xiàng)的應(yīng)用程序
添加CoreLib項(xiàng)目,創(chuàng)建ISayHello接口并為接口定義SayHello方法
namespace ISayHello{ public interface ISayHello { void SayHello(); } }
修改AssemblyLoad,添加CoreLib引用,并使其中的ClassA、ClassB、ClassC分別實(shí)現(xiàn)接口ISayHello,重新編譯,將AssemblyLoad.dll文件拷貝至D:\DLL\AssemblyLoad.dll
using CoreLib;namespace AssemblyLoad{ public class ClassA : ISayHello { public void SayHello() { Console.WriteLine("Hello.This is ClassA"); } } public class ClassB : ISayHello { public void SayHello() { Console.WriteLine("Hello.This is ClassB"); } } public class ClassC : ISayHello { public void SayHello() { Console.WriteLine("Hello.This is ClassC"); } } }
回到控制臺(tái)應(yīng)用程序AssemblyAndReflection,添加CoreLib引用,向App.config中添加配置節(jié):
<appSettings>
<add key="Test" value="ClassA"/>
</appSettings>修改Program中的Main方法:
class Program{ static void Main(string[] args) { string type = ConfigurationManager.AppSettings["Test"]; Assembly assembly = Assembly.LoadFrom(@"D:\DLL\AssemblyLoad.dll"); var q = from r in assembly.ExportedTypes where r.IsClass && typeof(ISayHello).GetTypeInfo().IsAssignableFrom(r.GetTypeInfo()) && r.Name == type select r; foreach (var t in q) { ISayHello s = (ISayHello)Activator.CreateInstance(t); s.SayHello(); } Console.ReadLine(); } }
啟動(dòng)程序,控制臺(tái)輸出Hello.This is ClassA,以上示例完成了根據(jù)配置文件中的參數(shù)調(diào)用指定類型下的方法,當(dāng)然,也可以把參數(shù)放在數(shù)據(jù)庫中。
項(xiàng)目結(jié)構(gòu)示意圖:
使用反射獲取類型的成員
獲取類型的成員
抽象基類System.Reflection.MemberInfo封裝了所有類型成員都通用的一組屬性。MemberInfo有許多派生類,每個(gè)都封裝了與特性類型成員相關(guān)的更多屬性,以下是這些類型的層次結(jié)構(gòu)圖:
調(diào)用類型的成員:
成員類型 | 調(diào)用(Invoke)成員需要調(diào)用的方法 |
---|---|
FieldInfo | 調(diào)用GetValue獲取字段的值 |
調(diào)用SetValue設(shè)置字段的值 | |
ConstructorInfo | 調(diào)用Invoke構(gòu)造類型的實(shí)例并調(diào)用構(gòu)造器 |
MethodInfo | 調(diào)用Invoke來調(diào)用類型的方法 |
PropertyInfo | 調(diào)用GetValue獲取屬性的get訪問器方法 |
調(diào)用SetValue獲取屬性的set訪問器方法 | |
EventInfo | 調(diào)用AddEventHandler來調(diào)用事件的add訪問器方法 |
調(diào)用RemoveEventHandler來調(diào)用時(shí)間的remove訪問器方法 |
使用綁定句柄減少進(jìn)程的內(nèi)存消耗
Type和MemberInfo類型的對(duì)象需要大量的內(nèi)存,如果將這些對(duì)象保存在集合當(dāng)中,可能對(duì)程序的性能產(chǎn)生負(fù)面的影響。如果需要保存/緩存大量的Type和MemberInfo對(duì)象,可以使用運(yùn)行時(shí)句柄代替對(duì)象以減少占用的內(nèi)存。System命名空間下有三個(gè)運(yùn)行時(shí)句柄類型:
RuntimeTypeHandle
RuntimeFieldHandle
RuntimeMethodHandle
以下是《CLR Via C#》第4版中的示例(博主已經(jīng)想不到更貼切的示例了):
class Program{ private const BindingFlags c_bf = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; static void Main(string[] args) { //顯示在任何反射操作之前堆的大小 Show("Before doing anything"); //為MScorlib.dll中的所有方法構(gòu)建MethodInfo對(duì)象緩存 List<MethodBase> methodInfos = new List<MethodBase>(); foreach (Type t in typeof(Object).Assembly.GetExportedTypes()) { //跳過所有泛型類型 if (t.IsGenericTypeDefinition) { continue; } MethodBase[] mb = t.GetMethods(c_bf); methodInfos.AddRange(mb); } //顯示當(dāng)綁定所有方法之后,方法的個(gè)數(shù)和堆的大小 Console.WriteLine("# of methods={0:N0}", methodInfos.Count); Show("After building cache of MethodInfo obejcts"); //為所有MethodInfo對(duì)象構(gòu)建RuntimeMethodHandle緩存 List<RuntimeMethodHandle> methodHandles = methodInfos.ConvertAll<RuntimeMethodHandle>(m => m.MethodHandle); Show("Holding MethodInfo and RuntimeMethodHandle cache"); GC.KeepAlive(methodInfos); //組織緩存被過早垃圾回收 methodInfos = null; //現(xiàn)在允許緩存垃圾回收 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); GC.KeepAlive(methodInfos); methodHandles = null; methodInfos = null; Show("After freeing MethodInfos and RuntimeMethodHandles"); Console.ReadLine(); } private static void Show(string s) { Console.WriteLine("Heap size={0,12:N0} - {1}", GC.GetTotalMemory(true), s); } }
http://www.cnblogs.com/Answer-Geng/p/7118335.html