在 c# 中反射技術應用廣泛,至于什么是反射.........你如果不了解的話,請看下段說明,否則請跳過下段。廣告一下:喜歡我文章的朋友請關注一下我的blog,這也有助于提高本人寫作的動力。
反射:當你背對一個美女或帥哥卻不能回頭仔細觀察研究時(純屬虛構,如有巧合、純屬雷同),一面小鏡子就能滿足你的需求。在 c# 編程過程中也經常遇到類似的情況:有一個別人寫的 dll 類庫你想使用卻沒程序文檔資料......此時通過 c# runtime 提供的功能,你可以把該 dll 類庫加載到你的程序中,并細細研究 dll 的每一部分內容,這就是 c# 中的反射。
個人認為反射最突出的優點或存在的合理性:在不修改程序原碼的情況下,實現程序功能的動態調整(runtime動態對象創建)
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
interface irun { void run(); } class person : irun { public void run() { console.writeline( "走,去lol啊!" ); } } class car : irun { public void run() { console.writeline( "嗚..........." ); } } class program { static void main( string [] args) { irun e = new person(); e.run(); console.readline(); } } |
如果將上面的run功能并不一定是由person來執行,有時需要是car有時需要person。常見的解決方案是添加 if 等判斷結構,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
static void main( string [] args) { console.writeline( "請輸入:car或person" ); string type = console.readline(); irun e = null ; if ( "car" == type) { e = new car(); } else if ( "person" == type) { e = new person(); } if ( null != e) e.run(); console.readline(); } |
這種結構確是解決了現在的需求,但并不健壯。隨著 irun 接口實現、相關類的繼承的增加,上面的判斷結構也會飛速增長。面向對象編程、設計模式均遵循的一大原則就是封裝變換,所以上面的程序無法很好的應對變化。在此我們并不涉及 “設計模式的” 的知識,因此下面的示例代碼只為簡化上面的程序、并未刻意套用設計模式相關知識。如下:
1
2
3
4
5
6
7
8
9
10
11
|
static void main( string [] args) { console.writeline( "請輸入:car或person" ); string type = console.readline(); string classpath = string .format( "namespace.{0}" , type); irun e = activator.createinstance( null , classpath).unwrap() as irun; if ( null != e) e.run(); console.readline(); } |
經過上面的修改,程序可自行根據用戶的輸入,通過activator.createinstance創建 irun 的實例,程序此處不會再隨 irun 的實現者增多這種問題的影響而發生變化。上面的這種優點就是通過反射得到的,也是我所認為的“反射存在的合理性”。
activator、assembly 實現反射方式創建對象
c#中反射方式創建對象可以通過 activator.createinstance(靜態)和 assembly.createinstance(非靜態)來實現,其中assembly.createinstance 內部調用的仍是activator.createinstance。
根據要動態創建的類型對象是否處于當前程序集之中,可將反射創建對象分為:創建程序集內的類型對象與創建程序集外的類型對象。
創建程序集內的類型對象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private static void reflectionirun1( string classname) { string classpath = string .format( "namespace.{0}" , classname); //參數 null ,指出所要創建類型對象位于當前程序集 var handler = activator.createinstance( null , classpath); irun e = (irun)handler.unwrap(); console.writeline(e.run()); } private static void reflectionirun2( string classname) { string classpath = string .format( "namespace.{0}" , classname); //typeof(irun).assembly 獲取 irun 類型所在的程序集 object obj = typeof (irun).assembly.createinstance( null , classpath); irun e = (irun)obj; console.writeline(e.run()); } |
創建程序集外的類型對象
項目中增加一個 類庫 (另一個程序集),如下圖:
添加一個 boss 類,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
namespace lib { public class boss { private string name = "老大" ; public string name{ get { return name;} } public string talk() { return "你們都被開除了......" ; } //老板不會算賬,總是多付錢,所以很有自知之明的將payfor設為private,防止外部人員調用 private int payfor( int total) { return total + 10; } } } |
獲取 一個 boss 對象前,首先添加對 lib 的引用,獲取示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private static void reflectionboss1() { string classpath = "lib.boss" ; //"lib" 參數指明要加載的程序集(即要創建的對象類型在哪個程序集中定義) var handler = activator.createinstance( "lib" , classpath); boss b = handler.unwrap() as boss; console.writeline(b.talk()); } private static void reflectionboss2() { string classpath = "lib.boss" ; //assembly.load("lib") 加載的程序集(即要創建的對象類型在哪個程序集中定義) var assembly = assembly.load( "lib" ); boss b = (boss)assembly.createinstance(classpath); console.writeline(b.talk()); } |
關于反射時clr如何查找并定位要加載的程序集,請參考msdn中關于反射相關的知識。
反射訪問字段、調用方法(屬性)
反射除可以幫我們動態創建對象外,還可幫我們動態訪問對象的方法(屬性)或字段,因 c# 版本不同具體方法會有變更或擴展,更深入內容請參考msdn。下面僅作簡單示例(標準用法)。
給老板改名,示例:
1
2
3
4
5
6
7
8
9
10
11
|
private static void reflectionboss1() { string classpath = "lib.boss" ; //"lib" 參數指明要加載的程序集(即要創建的對象類型在哪個程序集中定義) var handler = activator.createinstance( "lib" , classpath); boss b = handler.unwrap() as boss; //關鍵代碼 fieldinfo f = b.gettype().getfield( "name" , bindingflags.getfield | bindingflags.nonpublic | bindingflags.instance); f.setvalue(b, "小二" ); console.writeline( "{0}:{1}" , b.name, b.talk()); } |
輸出:
讓老板付錢:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private static void reflectionboss1() { string classpath = "lib.boss" ; //"lib" 參數指明要加載的程序集(即要創建的對象類型在哪個程序集中定義) var handler = activator.createinstance( "lib" , classpath); boss b = handler.unwrap() as boss; //關鍵代碼 methodinfo method = b.gettype().getmethod( "payfor" , bindingflags.invokemethod | bindingflags.nonpublic | bindingflags.instance); object money = method.invoke(b, new object [] { 10 }); console.writeline( "dw039:老大給我報銷10元錢車費......" ); console.writeline( "{0}:.....,算不清了,給你這些吧。" ,b.name); console.writeline( "dw039:......" ); console.writeline( "{0}:{1}" , b.name,money); console.writeline( "dw039:老大你真棒!" ); } |
輸出:
dynamic 與 反射 雙劍合璧
因為反射是運行時的類型操作,所以在編程時面臨類型不確定的問題。根據上一篇《c# 匿名對象(匿名類型)、var、動態類型 dynamic》講得 dynamic 動態類型結合我們編寫的反射程序,可以大大優化程序邏輯(訪問受保護級別限制的代碼不在此范圍內)。
上面代碼的優化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private static void reflectionboss1() { string classpath = "lib.boss" ; var handler = activator.createinstance( "lib" , classpath); dynamic b = handler.unwrap(); console.writeline(b.talk()); } private static void reflectionboss2() { string classpath = "lib.boss" ; var assembly = assembly.load( "lib" ); dynamic b = assembly.createinstance(classpath); console.writeline(b.talk()); } |
通過 dynamic 動態類型對象 b 來調用反射得到對象的屬性、方法可直接調用,從而省去了頻繁的類型轉換操作。
反射常見應用場景
應用場景我印象最深刻的是 ms petshop 示例,從sql server 數據庫切換到 oracle 數據庫時反射獲得不同的數據訪問層。然我實際項目中從未遇到過中途切換數據庫的情況,其他應用場景基本類似上面的示例。如果朋友你發現更多的應用場景,請給予補充,3ks。
反射的優缺點
優點:反射使程序更靈活
缺點:反射運行速度相對較慢
至于反射相比普通程序慢,我沒有進行過測試也不打算進行。現實情況是:ms提倡使用 dynamic、mvc流行、ms對clr不斷優化、機器性能的提升,所以你在開發中無需過多考慮反射的性能問題。如果你寫的程序運行速度出現了瓶頸(應首先確保自己程序寫的合理),研究一下數據庫優化、數據緩存、web緩存、負載均衡等技術我認為更實際一些。
原文鏈接:http://www.cnblogs.com/dw039/p/7476011.html