前言


  本篇討論程序集的加載及反射。主要涉及到System.Reflection.Assembly和System.Type兩個(gè)類,前者可以用于訪問指定程序集的相關(guān)信息,或把程序集加載到程序當(dāng)中,后者可以訪問任何數(shù)據(jù)類型的信息。以下,是本篇文章涉及的主要內(nèi)容。

移動(dòng)開發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)


程序集加載


  本節(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í),查找程序集的方式略有不同。

移動(dòng)開發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

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í)行原理:

  1. 調(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);
    }
  2. 調(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)用程序

  1. 添加CoreLib項(xiàng)目,創(chuàng)建ISayHello接口并為接口定義SayHello方法

    namespace ISayHello{    public interface ISayHello
        {        void SayHello();
        }
    }
  2. 修改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");
            }
        }
    }
  3. 回到控制臺(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)示意圖:

移動(dòng)開發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

使用反射獲取類型的成員


獲取類型的成員

  抽象基類System.Reflection.MemberInfo封裝了所有類型成員都通用的一組屬性。MemberInfo有許多派生類,每個(gè)都封裝了與特性類型成員相關(guān)的更多屬性,以下是這些類型的層次結(jié)構(gòu)圖:

移動(dòng)開發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

調(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