.NET編譯器的任務之一是為所有定義和引用的類型生產中繼資料描述。除了程式集中標準的中繼資料外,.NET平台允許程式員使用特性(attribute)把更多的中繼資料嵌入到程式集中。簡而言之,特性就是用於類型(比如類、介面、結構等)、成員(比如屬性、方法等)、程式集或模組的代碼注釋。
當瀏覽.NET命名空間時,將發現許多預定義特性,可以在應用程式中使用它們。此外,可以建立自訂特性,通過從Attribute派生出新類型進一步修飾類型的行為。當在代碼中應用特性時,如果它們沒有被另一個軟體顯示地反射,那麼嵌入的中繼資料基本沒什麼作用。反之,嵌入程式集的中繼資料介紹將被忽略不計,而並無害處。
限制特性使用:有時候需要建立這樣一個自訂特性,它只能被應用到選定的代碼元素上。如果希望限制自訂特性的應用範圍,需要在自訂特性的定義中應用[AttributeUsage]特性。[AttributeUsage]特性支援AttributeTargets枚舉值得任意組合(通過OR操作)。
自訂特性:你可以隨時建立自己
聲明一個特性:和C#的大多數元素一樣,特性是由類來實現的。要建立一個自訂特性,你須要從System.Attribute類派生你新的自訂特性的類。
public class BugFixAttribute:System.Attribute
你須要告訴編譯器這個特性可以被用在那種類型的元素上(特性目標)。使用 特性可以說明這一資訊。
[AttributeUsage(AttributeTargets.Class|
AttributeTargets.Constructor|
AttributeTargets.Field|
AttributeTargets.Method|
AttributeTartets.Property|
AllowMultiple=true)]
AttrbuteUsage特性是一個應用在特性上的特性,也就是一個元特性。也可以說,它提供了元-中繼資料,也就是於中繼資料相關的資料。你可以給AttributeUsage傳遞兩個參數。
第一個參數是一個標誌集合,它指明了特性的目標類型,在這個例子中,它們分別是類和 建構函式、欄位、方法和屬性。第二個參數是一個用來指明特定的元素是否可以接受多個這樣的特性的 標記。在這個例子中,AllowMultiple被設定為True,這表明了類的成員可以應用多個BugFixAttribute特性。
構造一個特性:特性可以接受兩種類型的參數:位置參數和具名引數。在BugFix特性的例子中,程式員的名字、Bug ID 和日期都是位置型參數,而備忘是具名引數。位置型參數是通過建構函式傳入的。它們必須按照建構函式中聲明的順序傳入。
反射(Reflection)
近年來,Reflection已經成為主流語言中必備的特色,其主要用途是執行時期提供類型資訊,一旦擁有這些資訊,設計人員可以輕易地建立出具備動態解析能力的應用程式,例如在執行時期以一個字串建立起對應的對象,抑或是以一個字串來調用函數,都可以由Reflection技術來達成。Reflection技術同時也是RAD開發工具的幕後功臣,運用Reflection技術,RAD開發工具可以取出某個組件的屬性與事件等資訊顯示於屬性工作表之上。在某些特殊應用上,Reflection更是扮演著極關鍵的角色,例如設計人員可以用Reflection取得某個類的資訊,再搭配.NET的CodeDOM技術來產生一個繼承至該類的類原始碼,動態為其實現某個介面,或是覆寫某個函數,抑或是結合Script語言來產生一個符合特定結構需求的對象。.NET Framework,Reflection是經由Type對象來操作,其中分成兩部分,一部分是提供該Type本身的資訊,例如Public、Sealed\Serializable、Attributes、Interfaces等等。此部分還算相當直觀,此處就不在贅述,另一部分則是取得該Type內的成員資訊,如方法、屬性、事件、變數、Attributes,這一系列函數的傳回值皆是MemberInfo類或其子嗣。
FieldInfo類代表成員變數,PropertyInfo類代表屬性,EventInfo類代表事件,MethodBase類細分為兩部分,ConstructorInfo類代表建立函數,MethodInfo類代表成員函數。有趣的是Type類本身也是MemberInfo類的子嗣,這種設計代表著Nested Type(外圍類型)也是Member的一員。
函數 說明
GetConstructor(s) 取得此類型的建立函數,其將回傳一個ConstructorInfo對象或數組
GetField(s) 取得此類型中成員變數,其將回傳一個FiledInfo對象或數組
GetMember(s) 取得此類中的成員,其類型可以是變數、事件、屬性、方法及Nested Type,其將回傳一個MemberInfo對象或數組
GetEvent(s) 取得此類型中的事件,其將回傳一個EventInfo對象或數組
GetProperty/GetProperties 取得此類型中的屬性,其將回傳一個PropertyInfo對象或數組
GetNestedType(s) 取得聲明於此類型內類型,其將回傳一個Type對象或數組
GetCustomAttibutes 取得綁定於此類型的Attitudes
利用這些函數,設計人員可以在執行時期取得某個類型中所有的成員資訊,也可以在非預設物件類型的情況下調用其成員函數或是設定某屬性值。
要讓中繼資料中的特性真正起作用,你需要一種訪問它們的方法,並且最好是在程式啟動並執行時候。在Reflection命名空間中的類和System.Type類一起為你提供了檢查和處理中繼資料的功能。
反射通常被用在以下4種任務中
查看中繼資料:這一功能可能會被希望顯示中繼資料的工具和輔助程式使用。
執行類型探索功能:這一功能允許你檢查程式集裡的類型並處理或執行個體化這些類型。在建立自訂指令碼時這一功能會非常有用。例如,你可能希望允許使用者使用一種指令碼語言和你的程式打交道,這種指令碼語言套件括Javascript或你自己建立的一種語言。
延遲綁定到方法和屬性上:這一功能允許程式員基於類型探索的功能來調用動態執行個體化對象上的屬性和方法。這也被稱為動態調用。
在運行時建立類型(反射代碼發射功能):反射最強大的用途是在運行時建立新的類型,然後使用這些類型執行任務。當一個任務在運行時建立的自訂類和運行速度比在編譯期建立的更加通用的代碼快得多的時候,你可能會這樣做。
查看中繼資料
下面通過發射讀取MyMath類中的中繼資料。一開始,你須要獲得一個MemberInfo類的對象。位於System.Reflection命名空間的這一對象的作用在於發現成員的特性,並提供訪問中繼資料的方法:
System.Reflection.MemberInfo inf = typeof(MyMath);
調用MyMath類型上typeof操作符將返回一個Type類型的對象,Type類型派生自MemberInfo類型。Type類是反射類的核心,它封裝了物件類型的表達形式。Type類是訪問中繼資料的主要方法。它派生自MemberInfo類並且封裝了關於類的成員的資訊(如方法、屬性、欄位、事件等資訊)。
下一步就是調用這個MemberInfo對象上的GetCustomAttributes方法,傳入你希望尋找的特性的類型。你將獲得一個對象的資料,資料中每一項的類型都是BugFixAttribute:
object[] attributes;
attributes = inf.GetCustomAttributes(typeof(BugFixAttribute), false);
類型探索(Type DisCovery)
你可以使用反射功能來瀏覽並檢查程式集裡的內容。可以尋找與模組相關的類型,於類型相關的方法、欄位、屬性和事件,以及類型中所用方法的簽名、類型支援的介面和類型的基類等資訊。
首先,你須要使用Assembly.Load()靜態方法動態地載入一個程式集。Assembly類封裝了實際的程式集自身,它提供了反射的功能。Load方法的其中一種簽名如下:
public static Assembly.Load(AssmeblyName)
在下面例子中,你將核心庫的名稱傳給了Load()方法。Mscorlib.dll包含了.NET架構的核心類:Assembly a = Assembly.Load("Mscorlib");
載入了程式集之後,你就可以調用GetTypes()方法,讓它返回Type對象的數組。Type對象是反射功能的核心。它表示了類型的聲明資訊(其中包括類、介面、數組、值和枚舉等):
Type[] types = a.GetTypes();
你可以使用一個Foreach迴圈顯示出從程式集返回的類型的數組。
反射類型
你也可以通過反射獲得Mcsorlib程式集裡的某一個類型。要實現這一功能,你可以使用typeOf或GetType()方法從程式集裡擷取一個類型。
Type theType = Type.GetType("System.Reflection.Assembly"); Console.WriteLine("\nSingle Type is {0}\n", theType); Console.Read();
輸出結果是:
Single Type is System.Reflection.Assembly
尋找所用類型成員
你可以使用Type類的GetMembers()方法獲得Assembly類型的所用成員,例如下面例子。這個 方法將會列出所有得方法、屬性和欄位。
尋找類型的方法
你可能會希望只關注於方法,而排除欄位、屬性等其他類型的資訊。要實現著以目標,你可以去掉對GetMembers()方法的調用。
MemberInfo[] mbrInfoArray = theType.GetMembers();
然後添加對GetMethods()方法的調用: mbrINnfoArray = theType.GetMethods();
現在輸出的結果中只包含方法:
尋找特定的類型成員
最後如果你希望進一步縮小範圍,你可以使用FindMembers方法找到類型的特定成員。例如,你可以將 搜尋範圍縮小到只包括名稱以"Get"開頭的方法。
要縮小搜尋範圍,你須要使用FindMembers方法,這個方法接受4個參數: