利用反射來動態建立執行個體和調用方法
.NET的一個強大功能是它可以通過一種稱為反射(reflection)的過程訪問應用程式的中繼資料。簡單地說,反射就是運行時查詢類型資訊的能力。.NET Reflection API 實際上是在System.Reflection命名空間中定義的一組類。這些類使你能夠按邏輯方式查看配件和類型資訊。我在這裡並不對反射的基本原理做講解,只是針對一些反射的基本用法做一下介紹。關於反射的定義還有一些理論上的東西請參見網上其它的文章或是msdn。 這裡我只講三個方面的應用:一是用反射來建立執行個體,二是用反射調用執行個體的方法,三是用反射調用執行個體的屬性。這三個是反射的最基本用法和用途,其它的用法比如調用執行個體的特性、委託等等,與這三個差不多,請讀者自己學習。 學習反射要具備很多基本知識,這裡並不準備講這些東西,只是開門見山的講其三個用法。 在此之前,我想說說Type這個類,不感興趣的讀者可以不看這一段。反射的核心是抽象的System.Type這個類。這個類是所有反射操作的根並且代表系統中的每個類型。這個類是訪問中繼資料的主要方式,它作為通向Reflection API的通道。Type類的成員用於擷取關於型別宣告的資訊。這些資訊包括為類型定義的所有構造器、方法、欄位、特性和事件,以及類所在的模組和配件。Type對象可以代表以下任何類型:類、實值型別、數組、介面、指標和枚舉。 下面的程式是一個名為ReflectionExample的簡單項目,裡麵包括MainClass.cs和InstanceClass.cs兩個檔案,MainClass裡面利用字串來建立InstanceClass類並且調用InstanceClass中的屬性和方法,其中包括調用預設建構函式和調用重載建構函式、調用無參數的成員函數和帶參數的成員函數四個方法,還有一個ReturnString屬性,程式如下。InstanceClass.cs檔案using System; namespace ReflectionExample{ ///<summary> /// InstanceClass 的摘要描述。 ///</summary> public class InstanceClass { private string returnString; public string ReturnString { get { return returnString ; } set { returnString = value ;} } public InstanceClass() { // // TODO: 在此加入建構函式的程式碼 // this.returnString = "Creat object without Parameter"; } public InstanceClass( string str ) { this.returnString = str; } public void FunctionWithoutParameter() { Console.WriteLine( "Function Without Parameter" ); } public void FunctionWithParameter( string str ) { Console.WriteLine( str ); } }}MainClass.cs檔案using System;using System.Reflection; namespace ReflectionExample{ ///<summary> /// Class1 的摘要描述。 ///</summary> class MainClass { private Type type = null; ///<summary> ///應用程式的主進入點。 ///</summary> [STAThread] static void Main(string[] args) { // // TODO: 在此加入啟動應用程式的程式碼 // Type type = typeof(InstanceClass);//利用typeof來獲得InstanceClass類型。 MainClass mainClass = new MainClass(type);//初始化MainClass類 mainClass.GetObjectMethod();//調用無參數的函數 mainClass.GetObjectMethod( "Function With Parameter" );//調用帶參數的函數 mainClass.GetObjectProperty();//調用預設建構函式來初始化InstanceClass中的ReturnString屬性 mainClass.GetObjectProperty("Creat object with Parameter");//調用重載建構函式來初始化InstanceClass中的ReturnString屬性 } public MainClass( Type type ) { this.type = type ; } public void GetObjectMethod() { try { //Activator.CreateInstance()調用類的預設建構函式來建立執行個體 object o = Activator.CreateInstance( type ); //利用MethodInfo類來獲得從指定類中的成員函數 MethodInfo mi = type.GetMethod("FunctionWithoutParameter" ); //調用此成員函數 mi.Invoke( o , null ); } catch( Exception ex ) { Console.WriteLine( ex.Message ); } finally { Console.ReadLine(); } } public void GetObjectMethod( string str ) { try { object o = Activator.CreateInstance( type ); //利用MethodInfo類來獲得從指定類中合格成員函數 MethodInfo mi = type.GetMethod( "FunctionWithParameter" , BindingFlags.Public|BindingFlags.Instance , null , new Type[]{ typeof(string) } , null ); mi.Invoke( o , new object[]{ str } ); } catch( Exception ex ) { Console.WriteLine( ex.Message ); } finally { Console.ReadLine(); } } public void GetObjectProperty( ) { try { object o = Activator.CreateInstance( type ); //利用PropertyInfo類來獲得從指定類中的屬性 PropertyInfo pi = type.GetProperty( "ReturnString" , typeof(string) ); //列印屬性值 Console.WriteLine( pi.GetValue( o , null ) ); } catch( Exception ex ) { Console.WriteLine( ex.ToString() ); } finally { Console.ReadLine(); } } public void GetObjectProperty( string str ) { try { //Activator.CreateInstance()調用類的重載建構函式來建立執行個體 object o = Activator.CreateInstance( type , new object[]{ "Creat object with Parameter" } ); PropertyInfo pi = type.GetProperty( "ReturnString" , typeof(string) ); Console.WriteLine( pi.GetValue( o , null ) ); } catch( Exception ex ) { Console.WriteLine( ex.ToString() ); } finally { Console.ReadLine(); } } }} 以上只是對反射基本用法的一個小例子,沒有展開講,不過我們可以看出其實基本思路已經很清晰了,用Type取得執行個體類型後,用Activator.CreateInstance()建立執行個體,再用MethodInfo來調用類中的方法,用PropertyInfo來調用類中的屬性,呼叫事件和參數等方法也是類似,請參見下面的說明。1. 使用Module瞭解包含模組的程式集以及模組中的類等,還可以擷取在模組上定義的所有全域方法或其它特定的非全域方法。
2.使用ConstructorInfo瞭解建構函式的名稱、參數、存取修飾詞(如public 或private)和實現詳細資料(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法來調用特定的建構函式。
3.使用MethodInfo瞭解方法的名稱、傳回型別、參數、存取修飾詞(如public 或private)和實現詳細資料(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法來調用特定的方法。
4.使用FieldInfo瞭解欄位的名稱、存取修飾詞(如public或private)和實現詳細資料(如static)等,並擷取或設定欄位值。
5.使用EventInfo瞭解事件的名稱、事件處理常式資料類型、自訂屬性、宣告類型和反射類型等,添加或移除事件處理常式。
6.使用PropertyInfo瞭解屬性的名稱、資料類型、宣告類型、反射類型和唯讀或可寫狀態等,擷取或設定屬性值。
7.使用ParameterInfo瞭解參數的名稱、資料類型、是輸入參數還是輸出參數,以及參數在方法簽名中的位置等。 上面的程式很簡單,唯一要說的就是MethodInfo mi = type.GetMethod( "FunctionWithParameter" , BindingFlags.Public |BindingFlags.Instance , null , new Type[]{ typeof(string) } , null );這個GetMethod中的參數說明了,如下:public virtual MethodInfo GetMethod( string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers);參數name 包含要擷取的方法名稱的 String。 bindingAttr 一個位屏蔽,由一個或多個指定搜尋執行方式的 BindingFlags 組成。 binder一個 Binder 對象,該對象定義一組屬性並啟用綁定,而綁定可能涉及選擇重載方法、強制參數類型和通過反射調用成員。 types表示此方法要擷取的參數的個數、順序和類型的 Type 對象數組。 擷取不使用參數的方法的 Type 類型的空數組(即 Type[] types = new Type[0])。 modifiersParameterModifier 對象數組,表示與 types 數組中的相應元素關聯的屬性。預設的聯編程式不處理此參數。 傳回值表示符合指定要求的方法的 MethodInfo 對象或是NULL; 其中bindingAttr中的BindingFlags定義如下。
| 成員名稱 |
說明 |
值 |
| CreateInstance 受 .NET Framework 精簡版的支援。 |
指定“反射”應該建立指定類型的執行個體。調用與給定參數匹配的建構函式。忽略提供的成員名。如果未指定尋找類型,將應用 (Instance |Public)。調用類型初始值設定項是不可能的。 |
512 |
| DeclaredOnly 受 .NET Framework 精簡版的支援。 |
指定只應考慮在所提供類型的階層層級上聲明的成員。不考慮繼承成員。 |
2 |
| Default 受 .NET Framework 精簡版的支援。 |
不指定綁定標誌。 |
0 |
| ExactBinding 受 .NET Framework 精簡版的支援。 |
指定提供參數的類型必須與對應形參的類型完全符合。如果調用方提供一個非空 Binder 對象,則“反射”將引發異常,因為這意味著調用方正在提供的 BindToXXX 實現將選取適當的方法。 “反射”建立一般型別系統的訪問規則模型。例如,如果調用方在相同的程式集內,則它不需要內部成員的特殊許可權。否則,調用方需要 ReflectionPermission。這與保護成員、私人成員等成員的尋找是一致的。一般原則是,ChangeType 應只執行永遠不會遺失資料的擴充強制。擴充強制的一個例子是將 32 位有符號整數值強製為 64 位元有符號整數值。這與窄縮強制不同,後者可能遺失資料。窄縮強制的一個例子是將 64 位元有符號整數強製為 32 位有符號整數。預設聯編程式忽略此標誌,而自訂聯編程式可以實現此標誌的語義。 |
65536 |
| FlattenHierarchy 受 .NET Framework 精簡版的支援。 |
指定應返回該階層以上的靜態成員。靜態成員包括欄位、方法、事件和屬性。不返回巢狀型別。 |
64 |
| GetField 受 .NET Framework 精簡版的支援。 |
指定應返回指定欄位的值。 |
1024 |
| GetProperty 受 .NET Framework 精簡版的支援。 |
指定應返回指定屬性的值。 |
4096 |
| IgnoreCase 受 .NET Framework 精簡版的支援。 |
指定當綁定時不應考慮成員名的大小寫。 |
1 |
| IgnoreReturn 受 .NET Framework 精簡版的支援。 |
在 COM interop 中用於指定可以忽略成員的傳回值。 |
16777216 |
| Instance 受 .NET Framework 精簡版的支援。 |
指定執行個體成員將包括在搜尋中。 |
4 |
| InvokeMethod 受 .NET Framework 精簡版的支援。 |
指定要調用一個方法。這可能不是建構函式或類型初始值設定項。 |
256 |
| NonPublic 受 .NET Framework 精簡版的支援。 |
指定非公用成員將包括在搜尋中。 |
32 |
| OptionalParamBinding 受 .NET Framework 精簡版的支援。 |
返回其參數計數與提供參數的數目匹配的成員集。此綁定標誌用於所帶參數具有預設值的方法和帶變數參數 (varargs) 的方法。此標誌應只與 Type.InvokeMember 一起使用。 具有預設值的參數僅用在省略尾部參數的調用中。它們必須是最後的參數。 |
262144 |
| Public 受 .NET Framework 精簡版的支援。 |
指定公用成員將包括在搜尋中。 |
16 |
| PutDispProperty 受 .NET Framework 精簡版的支援。 |
指定應調用 COM 物件的 PROPPUT 成員。PROPPUT 指定使用值的屬性設定函數。如果屬性同時具有 PROPPUT 和 PROPPUTREF,而且需要區分調用哪一個,請使用 PutDispProperty。 |
16384 |
| PutRefDispProperty 受 .NET Framework 精簡版的支援。 |
指定應調用 COM 物件的 PROPPUTREF 成員。PROPPUTREF 指定使用引用而不是值的屬性設定函數。如果屬性同時具有 PROPPUT 和 PROPPUTREF,而且需要區分調用哪一個,請使用 PutRefDispProperty。 |
32768 |
| SetField 受 .NET Framework 精簡版的支援。 |
指定應設定指定欄位的值。 |
2048 |
| SetProperty 受 .NET Framework 精簡版的支援。 |
指定應設定指定屬性的值。對於 COM 屬性,指定此綁定標誌與指定 PutDispProperty 和 PutRefDispProperty 是等效的。 |
8192 |
| Static 受 .NET Framework 精簡版的支援。 |
指定靜態成員將包括在搜尋中。 |
8 |
| SuppressChangeType 受 .NET Framework 精簡版的支援。 |
未實現。 |
131072 |
以上只是對反射作了一個簡單的介紹,大家可以按照上面的程式照貓畫虎,寫出自己需要的反射程式。也許你會有疑問,不是說反射可以在程式運行時動態建立類型嗎?上面的例子雖然也是動態建立的,可以還是要在知道類的名字和類中方法名的情況下才可以動態調用類中的函數,怎樣實現程式動態載入dll並且執行裡面的函數呢?這還需要介面和抽象類別的相關知識,請看我關於介面和抽象類別的這篇文章。如果你對介面和抽象類別已經很清楚了,那麼可以直接看下面這篇文章,裡面有具體怎麼利用反射和介面來實現動態載入dll的例子。(其實上面這兩篇文章我已經寫完了,但總是覺得有些地方寫得很膚淺,有些地方寫得不明不白,其實反射本身就是很靈活的東西,本人水平有限,所以決定暫時先不發布上面兩篇,等我對反射的理解更深時再發布。)