反射概念在網上處處都有,可是講到的具體的應用不多,一個重要的緣由是現實中真的不多用獲得它。引用msdn上對「反射」的解釋:html
"經過 System.Reflection 命名空間中的類以及 System.Type,您能夠獲取有關已加載的程序集和在其中定義的類型(如類、接口和值類型)的信息。 您也可使用反射在運行時建立類型實例,以及調用和訪問這些實例。"編輯器
這個解釋着實讓人難以理解,至少對新手來說,一頭霧水。那麼這篇文章我首先從概念下手,用一種儘可能易於理解的方式解釋一下反射究竟是個什麼東西。文章最後附加一個「反射」應用的demo,它能監放任何一個程序集中的任何一個Control(深度優先順序遍歷全部子控件)的全部事件信息。ide
在程序開發階段,若是咱們要使用一個類型(包括實例化該類型對象,訪問對象等等操做),分三個步驟:性能
圖1this
以上是咱們經常使用的開發步驟,幾乎不用去想爲何要這樣,每一個人都會。這個流程中第一個前提就是「要引用包含了這個類型的程序集」。假設某一次開發過程當中,咱們不能提早引用到包含這個類型的程序集(先不要否認這種狀況,只能說明你沒碰到),那麼咱們改怎麼寫代碼?正常狀況下,咱們訪問A類是這樣的(假設A類在A.dll程序集中):spa
1 A a=new A(); 2 a.PropertyName=」123」; 3 a.EventName+=a_EventName; 4 a.DoSomething();
正常編譯經過。可是如今咱們沒有引用A.dll,咱們改怎麼寫代碼?仍是像上面那樣寫嗎?不對,由於編譯通不過,編譯器會提示「缺乏對程序集的引用」(這個很容易理解,由於你沒有引用程序集,編譯器確定不會知道)。.net
以上就是咱們會碰到的一種狀況,即:插件
在有些時候,咱們可能會使用一種數據類型,可是開發階段並不能引用到包含該類型的程序集,包含了這個類型的程序集只能在程序運行起來以後,動態的引用進來。開發階段引用程序集若是叫「靜態引用程序集」,那麼運行時引用程序集就應該叫「動態引用程序集」了。後者會形成一個問題:咱們該怎麼寫代碼去訪問動態引用程序集中的類型?3d
圖2code
這個時候,反射的做用就出來了。反射可以讓咱們使用在編譯階段編譯器不知道的數據類型(注意是編譯器不知道,不是咱們不知道)。再舉前面使用A類型的例子,咱們在開發階段沒有引用A.dll程序集,所以下面的代碼沒法經過編譯:
1 A a=new A(); 2 a.PropertyName=」123」; 3 a.EventName+=a_EventName; 4 a.DoSomething();
可是,咱們知道程序運行以後,能夠動態引用到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 }
上面代碼可以經過編譯,咱們能夠經過以上代碼去訪問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 }
以上相似代碼並無錯誤,只是我以爲會給人誤導,反射的真正使用場合不在這裏(這裏徹底用不着,爲何不直接使用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
但願有幫助!