反射 你怎么看?


一 前言

  反射 這個名詞給我的第一印象就是 高深的感覺,雖然項目中有用到,只是通過反射枚舉來取值,后來發現居然不需要用反射............

 第二個印象就是性能不高,貌似對反射大家已經形成定向思維了,一扯到反射就和性能扯上關系.....

 本文拋開性能不談,因為關於性能,有太多的人做過測試了我就不說什么了,但我相信"存在即合理"    好了 進入正題!

 注:本人水平有限,難免會有理解不了或理解錯誤的地方,還望大家在評論中指出,我會馬上更正。謝謝了~

 

二 反射介紹

 2.1  什么是反射

  有關程序及其類型的數據被稱為元數據,它們保存在程序的程序集中。而程序在運行時,可以查看其他程序集或其本身的元數據。

一個運行的程序查看本身的元數據或其他程序的元數據的行為叫做反射。

2.2  為什么需要反射

     2.2.1  當你做一個軟件可以安裝插件的功能,你連插件的類型名稱都不知道,你怎么實例化這個對象呢?因為程序是支持插件的(第三方的),在開發的時候並不知道 。所以,無法在代碼中 New出來 ,但反射可以,通過反射,動態加載程序集,然后讀出類,檢查標記之后再實例化對象,就可以獲得正確的類實例。反射的目的就是為了擴展未知的應用。比如你寫了一個程序,這個程序定義了一些接口,只要實現了這些接口的dll都可以作為插件來插入到這個程序中。那么怎么實現呢?就可以通過反射來實現。就是把dll加載進內存,然后通過反射的方式來調用dll中的方法。

       2.2.2 在編碼階段不知道那個類名,要在運行期從配置文件讀取類名, 這時候就沒有辦法硬編碼new ClassName(),而必須用到反射才能創建這個對象.

2.3  與反射相關的類

  在了解反射前最好先把與反射相關類之間的關系,以及每個類大致負責哪些操作了解下,有助於對反射有一個結構化的認識,

這張圖是我自己在了解時畫的,比較原生態,各位就將就下吧,意思到了就行了.....

重點解釋下Type類

1 對於程序中用到的每個類型,CLR都會創建 一個包含這個類型信息的Type類型的對象。

2 程序中用到的每個類型都會關聯到獨立的Type類的對象。

3 不管創建的類型有多少個實例 ,只有一個Type對象會關聯到所有這些實例  

 

三 如何使用反射

  3.1 加載程序集的方法 

  以下3.1.1-3.1.3 內容摘抄於 @xiashengwang

      3.1.1 

  Assembly的Load方法

  在內部CLR使用Assembly的Load方法來加載這個程序集,這個方法與Win32的LoadLibray等價。在內部,Load導致CLR對程序集應用一個版本重定向策略。並在GAC中查找程序集,如果沒有找到,就去應用程序的基目錄,私有路徑目錄和codebase指定的位置查找。如果是一個弱命名程序集,Load不會向程序集應用重定向策略,也不會去GAC中查找程序集。如果找到將返回一個Assembly的引用,如果沒有找到則拋出FileNotFoundException異常。注意:Load方法如果已經加載一個相同標識的程序集只會簡單的返回這個程序集的引用,而不會去創建一個新的程序集。

    大多數動態可擴展應用程序中,Assembly的Load方法是程序集加載到AppDomain的首選方式。這種方式需要指定程序集的標識字符串。對於弱命名程序集只用指定一個名字。

  3.1.2

   Assembly的LoadFrom方法

   當我們知道程序集的路徑的場合,可以使用LoadFrom方法,它允許傳入一個Path字符串,在內部,LoadFrom首先調用AssemblyName的靜態方法GetAssemblyName。這個方法打開指定的文件,通過AssemblyRef元數據表提取程序集的標識,然后關閉文件。隨后,LoadFrom在內部調用Assembly的Load方法查找程序集。到這里,他的行為和Load方法是一致的。唯一不同的是,如果按Load的方式沒有找到程序集,LoadFrom會加載Path路徑指定的程序集。另外,Path可以是URL。如:

Assembly assembly = Assembly.LoadFrom(@"http://www.test.com/LibA.dll");

  3.3.3 

  Assembly的LoadFile方法

  這個方法初一看和LoadFrom方法很像。但LoadFile方法不會在內部調用Assembly的Load方法。它只會加載指定Path的程序集,並且這個方法可以從任意路徑加載程序集,同一程序集如果在不同的路徑下,它允許被多次加載,等於多個同名的程序集加載到了AppDomain中,這一點和上面的兩個方法完全不一樣。但是,LoadFile並不會加載程序集的依賴項,也就是不會加載程序集引用的其他程序集,這會導致運行時找不到其他參照DLL的異常。要解決這個問題,需要向AppDomain的AssemblyResolve事件登記,在回調方法中顯示加載引用的程序集。類似於這樣:

 
        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            if (args.Name != null)
            {
                return Assembly.LoadFrom(string.Format("{0}\\plugin\\{1}.dll", Application.StartupPath, new AssemblyName(args.Name).Name));
            }
            return null;
        }
 

特別注意:要測試LoadFile有沒有加載引用的DLL,切不可將DLL拷貝到應用程序的根目錄下測試,因為該目錄是CLR加載程序集的默認目錄,在這個目錄中如果存在引用的DLL,它會被加載,造成LoadFile會加載引用DLL的假象。可以在根目錄下新建一個子目錄如plugin,把引用的dll拷貝到這里面進行測試。

 

  3.2 反射調用同一程序集中的方法

 1     internal class PeopleInfo
 2     {
 3 
 4         public static  string Name = "Zery";
 5 
 6         public string PrintName(string name)
 7         {
 8             Console.WriteLine("被反射了,{0}", name);
 9             return name;
10         }
11     }
12 
13             //通過Type對象直接反射類並創建實例調用類中方法 
14             Type type = typeof(PeopleInfo);
15             //創建實例
16             var peopleType = (PeopleInfo)type.Assembly.CreateInstance(type.FullName);
17             //調用方法
18             string name = peopleType.PrintName("Zery");
19             
20             //取字段的值
21             FieldInfo fields = type.GetField("Name");
22             object value = fields.GetValue(peopleType);
23 
24             Console.WriteLine(value);

結果 

這種反射好像沒什么實際意義,因為可以直接New 一個實例出來,還比反射來得快,但這里還是寫下,以證名它是存在的

 

3.3 反射調用同一工程中不同程序集中方法

 

 1     public class UserInfo
 2     {
 3         public string name = "I'm Zery";
 4         public int age = 22;
 5         public string sex = "Super Man";
 6 
 7         public string Name
 8         {
 9             get { return name; }
10             set { name = value; }
11         }
12 
13         public int Age
14         {
15             get { return age; }
16             set { age = value; }
17         }
18         public string Sex
19         {
20             get { return sex; }
21             set { sex = value; }
22         }
23 
24         public string PrintName(string name)
25         {
26             Console.WriteLine("這是反射調用的Name:{0}", name);
27             return name;
28         }
29 
30 }
31 
32 
33             //一定要在項目中添加引用
34             Assembly assemblyRum = Assembly.Load("PersonInfo");
35             Type runType = assemblyRum.GetType("PersonInfo.UserInfo");
36             //創建實例,//Activator: 在本地或從遠程創建對象類型,或獲取對現有遠程對象的引用
37             var obj = Activator.CreateInstance(runType);
38             //獲取方法
39             MethodInfo runMethod = runType.GetMethod("PrintName");
40             //調用方法
41             //注意Invoke方法的兩個參數,第一個為類的實例對象如果方法是靜態的,則忽略此參數,第二個為方法的參數 
42             runMethod.Invoke(obj, new object[] { "Zhang" });
43             
44             //獲取字段
45             FieldInfo field = runType.GetField("name");
46             object value = field.GetValue(obj);
47             Console.WriteLine("獲取的字段值:{0}",value);

結果 

 

 3.3 反射調用指定路徑程序集中的方法

  

namespace AssembleTest
{
    public class Assemble
    {
        
        public string name = "I'm Zery";
        public int age = 22;
        public string sex = "Super Man";

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public int Age {
            get { return age; }
            set { age = value; }
        }
        public string Sex
        {
            get { return sex; }
            set { sex = value; }
        }

        public string PrintName(string name)
        {
            Console.WriteLine("這是反射調用的{0}", name);
            return name;
        }

        public void PrintAge(int age)
        {
            Console.WriteLine("這是反射調用的:{0}", age);
        }

        public string ReturnValue(int age, string name, string sex)
        {
            return string.Format("給反射的返回值:名字:{0},年齡:{1},性別{2}", name, age, sex);
        }

        public static void PrintSex(string sex)
        {
            Console.WriteLine("這是靜態的方法:{0}", sex);
        }
    }
}



      //加載指定路徑下的程序集
            Assembly assembly = Assembly.LoadFile(@"F:\TestFile\AssembleTest.dll");

            Assembly formAssembly = Assembly.LoadFrom(@"F:\TestFile\AssembleTest.dll");
            

            Type types = assembly.GetType("AssembleTest.Assemble");

            MethodInfo method = types.GetMethod("PrintName");
            MethodInfo staticMethod = types.GetMethod("PrintSex");
            
            //反射創建類的實例
            Object obj = assembly.CreateInstance("AssembleTest.Assemble"); //需要加名稱空間
            //反射創建類的實例 
            //Activator: 在本地或從遠程創建對象類型,或獲取對現有遠程對象的引用
            Object acrivatorObj = Activator.CreateInstance(types);
            
            //注意Invoke方法的兩個參數,第一個為類的實例對象如果方法是靜態的,則忽略此參數,第二個為方法的參數 
            method.Invoke(acrivatorObj, new object[] { "Zery" });
            
            //調用靜態方法 第一個參數可以省略
            staticMethod.Invoke(null, new object[] { "" });

            //獲取字段值
            FieldInfo field = types.GetField("name");
            var value = field.GetValue(obj);
            Console.WriteLine("這是字段值:{0}",value);

 

 結果 

 

 寫了在三種不同情況下通過反射調用程序集中的方法,與字段,反射還有很多功能,如 調用 事件,接口,獲取類的屬性等等,我寫的這里只是反射的冰山一角,若要了解更多,那就得大家自己動手了。

 

四 總結

  雖然反射在性能上有影響,但不能以此為借口,讓自己不去了解反射。沒寫這篇文章之前,看到項目中有一段反射調用其它程序集中方法的代碼,只知道結果是調用了方法,卻並不知道是如何一步步調用的,而當我寫完時,就已經可以將整個調用的流程了解的比較透徹了,所以覺得 寫一篇文章,結果並不重要,重要的是寫的這個過程會讓你收獲甚多。

程序員應該走出自己的舒適區,讓自己不斷的成長。

 

    如果您覺得本文有給您帶來一點收獲,不妨點個推薦,為我的付出支持一下,謝謝~

    如果希望在技術的道路上能有更多的朋友,那就關注下我吧,讓我們一起在技術的路上奔跑

 

 成長在於積累 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM