.Net開發筆記(二十一) 反射在.net中的應用


反射概念在網上到處都有,但是講到的具體的應用很少,一個重要的原因是現實中真的很少用得到它。引用msdn上對“反射”的解釋:

"通過 System.Reflection 命名空間中的類以及 System.Type,您可以獲取有關已加載的程序集和在其中定義的類型(如類、接口和值類型)的信息。 您也可以使用反射在運行時創建類型實例,以及調用和訪問這些實例。"

這個解釋着實讓人難以理解,至少對新手來講,一頭霧水。那么這篇文章我首先從概念下手,用一種盡量易於理解的方式解釋一下反射到底是個什么東西。文章最后附加一個“反射”應用的demo,它能監聽任何一個程序集中的任何一個Control(深度優先順序遍歷所有子控件)的所有事件信息。

在程序開發階段,如果我們要使用一個類型(包括實例化該類型對象,訪問對象等等操作),分三個步驟:

  • 添加包含了這個類型程序集的引用(.net自帶的類型程序集默認已引用);
  • 代碼中直接使用該類型(用一種文本字符的形式,比如類型名、方法名、屬性名);
  • 編譯正確通過,程序運行。

圖1

以上是我們常用的開發步驟,幾乎不用去想為什么要這樣,每個人都會。這個流程中第一個前提就是“要引用包含了這個類型的程序集”。假設某一次開發過程中,我們不能提前引用到包含這個類型的程序集(先不要否定這種情況,只能說明你沒碰到),那么我們改怎么寫代碼?正常情況下,我們訪問A類是這樣的(假設A類在A.dll程序集中):

1 A a=new A();
2 a.PropertyName=”123”;
3 a.EventName+=a_EventName;
4 a.DoSomething();
View Code

正常編譯通過。但是現在我們沒有引用A.dll,我們改怎么寫代碼?還是像上面那樣寫嗎?不對,因為編譯通不過,編譯器會提示“缺少對程序集的引用”(這個很容易理解,因為你沒有引用程序集,編譯器肯定不會知道)。

以上就是我們會碰到的一種情況,即:

在有些時候,我們可能會使用一種數據類型,但是開發階段並不能引用到包含該類型的程序集,包含了這個類型的程序集只能在程序運行起來之后,動態的引用進來。開發階段引用程序集如果叫“靜態引用程序集”,那么運行時引用程序集就應該叫“動態引用程序集”了。后者會造成一個問題:我們該怎么寫代碼去訪問動態引用程序集中的類型

圖2

這個時候,反射的作用就出來了。反射能夠讓我們使用在編譯階段編譯器不知道的數據類型(注意是編譯器不知道,不是我們不知道)。再舉前面使用A類型的例子,我們在開發階段沒有引用A.dll程序集,因此下面的代碼無法通過編譯:

1 A a=new A();
2 a.PropertyName=”123”;
3 a.EventName+=a_EventName;
4 a.DoSomething();
View Code

 但是,我們知道程序運行之后,可以動態引用到A.dll,那么現在代碼中怎么使用A類型呢?看下面的代碼:

 1 Assembly assembly=Assembly.LoadFile(“C:\\A.dll”); //動態引用A.dll
 2 Type t = assembly.GetType(“ReflectionTestNS.A”); //獲取A類型在程序集中的信息
 3 object oj=Activator.CreateInstance(t); //類似new A()
 4            PropertyInfo p = t.GetProperty("PropertyName"); 
 5             if (p != null)
 6             {
 7                 p.SetValue(oj, “123”, null);  //類似oj.PropertyName=”123”
 8             }
 9             EventInfo ei = t.GetEvent("EventName"); 
10             if (ei != null)
11             {
12                 Type tt = ei.EventHandlerType;
13                 ei.AddEventHandler(oj, Delegate.CreateDelegate(tt, this, "oj_EventName")); 
14                 //類似oj.EventName+=oj_EventName
15 }
16             MethodInfo m = t.GetMethod("DoSomething", new Type[] { });
17             if (m != null)
18             {
19                 m.Invoke(oj, null); //類似oj.DoSomething()
20             }
View Code

上面代碼能夠通過編譯,我們可以通過以上代碼去訪問A.dll程序集中的A類型,即使在開發階段我們沒有A.dll的引用。需要注意的幾點有:

1)雖然我們在開發階段不能引用到A.dll程序集,但是我們應該對A.dll中的類型有了解,知道命名空間,知道數據類型名稱,知道方法名稱參數類型,知道事件名稱委托類型等等,也就是說,雖然編譯器不知道A.dll中的類型信息,我們開發人員必須知道A.dll中的類型信息,這樣以來,我們才能利用“反射”加上“文本字符”作為標示去訪問這個類型。

2)1)中規定的開發人員必須了解A.dll中的類型信息,僅僅是當你需要詳細的使用一個類型對象時,如果你只需要獲取A.dll中的有哪些類型、每個類型有哪些方法參數屬性事件等,然后將他們的信息顯示出來,完全沒必要知道A.dll中的類型信息,比如VS中編輯器的智能提示功能,或者Reflector等利用反射實現數據集中類型信息顯示的軟件,它們的開發人員知道你的程序集信息嗎?不知道,但是還是能工作很好。但是就上面“使用A類型”的例子來講,你必須知道A.dll中的A類型中有個叫EventName的事件,你才能給它的對象注冊事件,否則可以說你根本使用不了A類型的對象。

3)編譯階段,對象和方法就可以關聯起來(比如a.DoSomething()能通過編譯),這種如果稱之為“早期綁定”(early binding),那么通過反射將對象和方法關聯起來就稱為“晚期綁定”(late binding)。前者在編譯階段編譯器可以檢查正確性,后者編譯器無能為力,因為編譯器不知道A.dll的任何信息。

一張圖區分兩種訪問程序集中類型的區別:

圖3

個人認為,正常開發中用不到反射,所以盡量避免使用反射(反射有缺陷,運行性能編譯階段不能檢查正確性等),本系列博客(十七)中講到的擴展應用程序,就使用到了反射,文中指出將插件打包成dll程序集后,放入宿主程序的plugins目錄中,宿主程序啟動后,會動態引用plugins目錄中的程序集,動態創建插件類型實例,然后訪問它。那么如果你是宿主程序的開發人員,你會在開發階段引用到第三方開發的插件程序集dll文件嗎?不能,但是你還是得在代碼中使用它的類型。

注:上面擴展應用程序中不全使用反射去訪問動態引用程序集中的類型,因為它使用到了一個IPlugin的接口,動態實例化插件對象后,是使用IPlugin接口引用這個對象,之后所有的都是通過這個接口去訪問對象(之后沒有使用到反射),它避免了使用反射的性能問題和在編譯階段能夠檢查程序的正確性(開發階段宿主程序能夠引用IPlugin接口程序集),這個也是必須使用反射場合的一種改進,后續有機會我會詳細說明。

另外網上有很多講述反射的文章,都是用類似如下代碼作為反射應用實例,

 1 void btn1_Click(object sender,EventArgs e)
 2 {
 3 Type t = typeof(Button);
 4 //或者
 5 Type t = btn1.GetType();
 6 PropertyInfo p = t.GetProperty(“Text”);
 7 if(p!=null)
 8 {
 9     p.SetValue(btn1,”123”,null); //利用反射編輯btn1的Text屬性
10 }
11 }
View Code

以上類似代碼並沒有錯誤,只是我覺得會給人誤導,反射的真正使用場合不在這里(這里完全用不着,為什么不直接使用btn1.Text=”123”呢?),看多了,人們就會認為反射就是這作用,用在這里。

Demo中包含了兩個項目,一個是簡單的說明了正常方法使用BackgroundWorker這個類型,和動態引用程序集動態創建BackgroundWorker類型對象(假裝開發階段沒有引用包含BackgroundWorker類型的程序集),兩者的區別。另一個項目能夠動態引用程序集,並且動態實例化Control類實例,關鍵還能監聽任何控件的所有事件,然后輸出事件信息,這個有點復雜,不僅僅使用到了System.Reflection命名空間中的類型,還用了System.Reflection.Emit命名空間中的類型,后者可以動態創建類型,由於每個控件的每個事件類型不一樣,並且個數還不確定,所以我們沒有辦法事先定義一個通用的事件注冊者,只能挨個為每個事件動態創建一個事件注冊者類。第二個項目流程見下圖:

圖4

第二個項目參見了CodeProject上老外的一篇文章(http://www.codeproject.com/Articles/3317/ControlInspector-monitor-Windows-Forms-events-as-t),注釋請參見我的,代碼中有詳細的中文解釋。

Demo截圖:

圖5 靜態引用程序集訪問類型 和 動態引用程序集訪問類型的區別

圖6 反射應用

總之,反射能夠讓你使用在編譯階段還不可達的程序集(類型)。

源碼下載地址:http://files.cnblogs.com/xiaozhi_5638/ReflectionTest.rar

 

希望有幫助!


免責聲明!

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



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