技術簡介
反射技術與設計模式
反射(Reflection)是.NET中的重要機制,通過反射,可以在運行時獲得.NET中每一個類型(包括類、結構、委託、接口和枚舉等)的成員,包括方法、屬性、事件,以及
構造函式等。還可以獲得每個成員的名稱、
限定符和參數等。有了反射,即可對每一個類型了如指掌。如果獲得了
構造函式的信息,即可直接創建對象,即使這個對象的類型在編譯時還不知道。
程式結構
程式代碼在編譯後生成可執行的套用,我們首先要了解這種可執行應用程式的結構。
應用程式結構分為
應用程式域—
程式集—模組—類型—成員幾個層次,
公共語言運行庫載入器管理應用程式域,這種管理包括將每個程式集載入到相應的應用程式域以及控制每個程式集中類型層次結構的記憶體布局。
程式集包含模組,而模組包含類型,類型又包含成員,反射則提供了封裝程式集、模組和類型的對象。我們可以使用反射動態地創建類型的實例,將類型綁定到現有對象或從現有對象中獲取類型,然後調用類型的方法或訪問其欄位和屬性。反射通常具有以下用途。
(1)使用Assembly定義和載入
程式集,載入在
程式集清單中列出模組,以及從此程式集中查找類型並創建該類型的實例。
(2)使用Module了解包含模組的
程式集以及模組中的類等,還可以獲取在模組上定義的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解構造函式的名稱、參數、訪問
修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法來調用特定的構造函式。
(4)使用MethodInfo了解方法的名稱、返回類型、參數、訪問
修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法來調用特定的方法。
(5)使用FiedInfo了解欄位的名稱、訪問
修飾符(如public或private)和實現詳細信息(如static)等,並獲取或設定欄位值。
(6)使用EventInfo了解事件的名稱、事件處理程式
數據類型、
自定義屬性、聲明類型和反射類型等,添加或移除事件處理程式。
(7)使用PropertyInfo了解屬性的名稱、
數據類型、聲明類型、反射類型和唯讀或可寫狀態等,獲取或設定屬性值。
(8)使用ParameterInfo了解參數的名稱、
數據類型、是輸入參數還是輸出參數,以及參數在方法簽名中的位置等。
System.Reflection.Emit命名空間的類提供了一種特殊形式的反射,可以在運行時構造類型。
反射也可用於創建稱為類型瀏覽器的應用程式,使用戶能夠選擇類型,然後查看有關選定類型的信息。
此外,Jscript等語言
編譯器使用反射來構造
符號表。System.Runtime.Serialization命名空間中的類使用反射來訪問數據並確定要永久保存的欄位,System.Runtime.Remoting命名空間中的類通過序列化來間接地使用反射。
反射技術示例
下面是反射技術的示例,我們可以在程式運行時動態實例化對象,獲得對象的屬性,並調用對象的方法。
1Namespace ReflectionExample
2{
3 class Class1
4 {
5 [STAThread]
6 static void Main (string [ ] args)
7 {
8 System.Console.WriteLine(“列出程式集中的所有類型”);
9 Assembly a = Assembly.LoadFrom (“ReflectionExample.exe”);
10 Type[ ] mytypes = a.GetTypes( );
11
12 Foreach (Type t in mytypes)
13 {
14 System.Console.WriteLine ( t .Name );
15 }
16 System.Console.ReadLine ( );
17 System.Console.WriteLine (“列出HellWord中的所有方法” );
18 Type ht = typeof(HelloWorld);
19 MethodInfo[] mif = ht.GetMethods();
20 foreach(MethodInfo mf in mif)
21 {
22 System.Console.WriteLine(mf .Name);
23 }
24 System.Console.ReadLine();
25 System.Console.WriteLine("實例化HelloWorld,並調用SayHello方法");
26 Object obj = Activator.CreateInstance(ht);
27 string[] s = {"zhenlei"};
28 Object bojName = Activator.CreateInstance(ht,s);
29 BindingFlags flags = (BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Static|BindingFlags.Instance|BindingFlags.DeclaredOnly);
30 MethodInfo msayhello = ht.GetMethod("SayHello");
31 msayhello.Invoke(obj,null);
32 msayhello.Invoke(objName,null);
33 System.Console.ReadLine();
34 }
35 }
36}
1using System;
2namespace ReflectionExample
3{
4 public class HelloWorld
5 {
6 string myName = null;
7 public HelloWorld(string name)
8 {
9 myName = name;
10 }
11 public HelloWorld() : this(null)
12 {}
13 public string Name
14 {
15 get
16 {
17 return myName;
18 }
19 }
20 public void SayHello()
21 {
22 if(myName == null)
23 {
24 System.Console.WriteLine("Hello World");
25 }
26 else
27 {
28 System.Console.WriteLine("Hello," + myName);
29 }
30 }
31 }
32}
33
在設計模式實現中使用反射技術
採用反射技術可以簡化工廠的實現。
(1)工廠方法:通過反射可以將需要實現的子類名稱傳遞給工廠方法,這樣無須在子類中實現類的實例化。
採用反射技術可以簡化工廠代碼的複雜程度,在.NET項目中,採用反射技術的工廠已經基本代替了工廠方法。
採用反射技術可以極大地簡化對象的生成,對以下設計模式的實現也有很大影響。
(1)命令模式:可以採用命令的類型名稱作為參數直接獲得命令的實例,並且可以動態執行命令。
(2)享元模式:採用反射技術實例化享元可以簡化享元工廠。
委託技術與設計模式
委託技術是.NET引入的一種重要技術,使用委託可以實現對象行為的
動態綁定,從而提高設計的靈活性。
委託技術
.NET
運行庫支持稱為“委託”的
引用類型,其作用類似於C++中的
函式指針。與
函式指針不同,委託實例獨立於其封裝方法的類,主要是那些方法與委託類型兼容。另外,
函式指針只能引用
靜態函式,而委託可以引用靜態和實例方法。委託主要用於.NET Framework中的事件處理程式和
回調函式。
所有委託都從System.Delegate繼承而來並且有一個調用列表,這是在
調用委託時所執行方法的一個連結列表。產生的委託可以用匹配的簽名引用任何方法,沒有為具有返回類型並在調用列表中包含多個方法的委託定義返回值。
可以使用的委託Cimbine及Remove方法在其調用列表中添加和移除方法。若要
調用委託,可使用Invoke方法,或者使用BeginInvoke和EndInvoke方法
異步調用委託。委託類的實現由
運行庫提供,而不由用戶代碼提供。
委託聲明定義一個類,它是從System.Delegate類派生的類。委託實例封裝了一個調用列表,其中列出了一個或多個方法,每個方法稱為一個可調用實體。對於實例方法,可調用實體由一個實例和該實例的方法組成;對於
靜態方法,可調用實體僅由一個方法組成。如果用一組合適的參數來調用一個委託實例,則該委託實例所封裝的每個可調用實體都會被調用,並且使用上述同一組參數。
委託實例的一個有用的屬性是它既不知道,也不關心其封裝方法所屬類的詳細信息,對它來說最重要的是這些方法與該委託的類型兼容。即只要方法的返回類型和參數表是相同的,則方法與委託類型兼容,方法的名稱不一定要與委託類相同。
定義和使用委託分為聲明、實例化和調用3個步驟。委託用委託聲明語法聲明,如:
delegate void myDelegate( );
聲明一個名為myDelegate的委託,它不帶參數並且不返回任何結果,如:
class Test
{
static void F( )
{
System.Console.WriteLine (“Test.F”);
}
static void Main ( )
{
myeDelegate d = new myDelegate (F);
d ( );
}
}
創建一個myDelegate實例,然後立即調用它。這樣做並沒有太大的意義,因為直接調用方法會更簡單。當涉及其匿名特性時,委託才能真正顯示出其效果,如:
void MultiCall (myDelegate d, int count ) {
for (int I = 0; I < count; I++) {
d( );
}
}
顯示一個重複調用 myDelegate的MultiCall 方法,這個方法不知道,也不必知道myDelegate的目標方法的類型、該方法具有的可訪問性或者是否為
靜態。對它來說最重要的是目標方法與myDelegate兼容。
示例
下面的例子說明了委託的實現,代碼如下:
1using System;
2namespace DelegateExample
3{
4 public class TemplateMethod
5 {
6 public delegate float Comp(float a,float b);
7 public Comp myComp;
8 public TemplateMethod()
9 {}
10 public float DoComp(float[] f)
11 {
12 float nf = float.NaN;
13 foreach(float df in f)
14 {
15 if(float.IsNaN(nf))
16 nf = df;
17 else
18 nf = myComp(nf,df);
19 }
20 return nf;
21 }
22
23 }
24}
委託技術與GOF設計模式中委託的關係
需要指出的是,.NET中的委託技術與GOF在《設計模式》中所提列的委託的意圖一致,但在實現方法上有相當大的區別。.NET中的委託更進一步地降低了對象間的
耦合性,將
靜態的組合關係變為運行時的動態組合關係。
GOF在《設計模式》中定義的委託是:“委託是一種組合方法,它使組合具有與繼承同樣的復用能力。在委託方式下,有兩個對象參與處理一個請求,接受請求的對象將操作委託給它的代理者(delegate),它類似於子類將請求交給它的父類處理。使用繼承時,被繼承的操作總能引用接受請求的對象。在C++中通過this
成員變數,在Smalltalk中則通過self。委託方式為了得到同樣的效果,接受請求的對象將自身傳給被委託者(代理人),使被委託的操作可以引用接受請求的對象。”
如果採用.NET的委託技術,上述結構可以更加靈活。Window不引用Rectangle即可實現Area的計算,為此首先聲明一個計算面積的委託定義,示例代碼如下:
public delegate float Darea();
然而在Window類中聲明與這個代理一致的接口:
class Window
{
public Darea Area;
}
這裡不需要引用Rectangle類,只是在執行時
動態綁定即可:
Rectangle rc = new Rectangle();
Window w = new Window();
w.Area = new Darea(rc.Area);
這樣當調用w的Area時,實際調用的是Reactangel的Area方法。從實現意圖上看,.NET的委託更好地實現了GOF所闡述的意圖,結構上也更為靈活。但這兩種委託解決的不是一個層面的問題,GOF的委託強調的是一種策略,而.NET和委託技術則是具體實現。
委託技術與設計模式實現
採用委託技術可以進一步實現用組合代替繼承的思路,很多採用繼承實現的關係可以採用委託實現。採用委託可以簡化下列設計模式的使用。
(1)模板方法:這種方法採用繼承實現具體方法,採用委託可以動態實現方法的組合。
(2)觀察者:可以使用事件委託實現觀察者與主題之間的通信。
(3)中介者:使用委託可以去除工件與中介者之間的耦合關係。