[assembly: MyAttr(1)] // 應用于程序集[module: MyAttr(2)] // 應用于模塊 [type: MyAttr(3)] // 應用于類型internal sealed class SomeType<[typevar: MyAttr(4)] T> { // 應用于泛型類型變量 [field: MyAttr(5)] // 應用于字段 public Int32 SomeField = 0; [return: MyAttr(6)] // 應用于返回值 [method: MyAttr(7)] // 應用于方法 public Int32 SomeMethod( [param: MyAttr(8)] // 應用于方法參數(shù) Int32 SomeParam) { return SomeParam; } [property: MyAttr(9)] // 應用于屬性 public String SomeProp { [method: MyAttr(10)] // 應用于get訪問器方法 get { return null; } } [event: MyAttr(11)] // 應用于事件 [field: MyAttr(12)] // 應用于編譯器生成的字段 [method: MyAttr(13)] // 應用于編譯器生成的add&&remove方法 public event EventHandler SomeEvent;} [AttributeUsage(AttributeTargets.All)]public class MyAttr : Attribute { public MyAttr(Int32 x) { }} 前面介紹了如何應用一個attribute,接下來看看attribute到底是什么?
attribute實際是一個類的實例。為了符合"公共語言規(guī)范"(CLS)的要求,attribute類必須直接或間接地從公共抽象類System.Attribute派生。C#只允許使用符合CLS規(guī)范的attribute。attribute類是可以在任何命名空間中定義的。
如前所述,attribute是類的一個實例。類必須有一個公共構造器,這樣才能創(chuàng)建它的實例。所以,將一個attribute應用于一個目標元素時,語法類似于調(diào)用類的某個實例構造器。除此之外,語言可能支持一些特殊的語法,允許你設置于attribute類關聯(lián)的公共字段或?qū)傩浴1热纾覀儗llImport這個attribute應用于GetVersionEx方法:[DllImport("kernel32",CharSet = CharSet.Auto, SetLastError = true)]這一行代碼語法表面上很奇怪,因為調(diào)用構造器時永遠不會出現(xiàn)這樣的語法。查閱DllImportAttbute類的文檔,會發(fā)現(xiàn)它只接受一個String類型的參數(shù)。在這個例子中,"Kernel32"這個String類型的參數(shù)已經(jīng)傳給它了。構造器的參數(shù)稱為"定位參數(shù)",而且是強制性的。也就是說,應用attribute時,必須指定參數(shù)。
那么,另外兩個"參數(shù)"是什么?這種特殊的語法允許在DllImportAttbute對象構造好之后,設置對象的任何公共字段和屬性。在這個例子中,當DllImportAttbute對象構造好,而且將"Kernel32"傳給構造器之后,對象的公共實例字段CharSet和SetListError被分別設置為CharSet.Auto和true。用于設置字段或?qū)傩缘?參數(shù)"被稱為"命名參數(shù)"。這種參數(shù)是可選的,因為在應用attribute的一個實例時,不一定要指定命名參數(shù)。 另外,還可以將多個attribute應用于一個目標元素。將多個attribute應用一個目標元素時,attribute的順序是無關緊要的。在C#中,可將每個attribute都封閉到一對方括號中,也可以在一對方括號中封閉多個以逗號分隔的attribute。 二、定義自己的attibude類 現(xiàn)在我們已經(jīng)知道attribute是從System.Attribute派生的一個類的實例,并知道如何應用一個attribute。接著我們來研究下如何定制attribute。假定你是Microsoft的一位員工,并負責為枚舉類型添加位標志(biit flag)支持。為此,我們,要做的第一件事情是定義一個FlagAttribute類:namespace System {public class FlagsAttribute : System.Attribute {public FlagsAttribute(){}}} 注意,F(xiàn)lagsAttribute類是從Attribute繼承的。所以,才使FlagsAttribute類成為符合CLS要求的一個attribute。除此之外,注意類名有一個Attribute后綴;這是為了保持于標準的相容性,但并不是必須的。最后,所有的非抽象attribute都至少要包括一個公共構造器。 提示:attribute類型是一個類,但這個類應該非常簡單。這個類應該只提供一個公共構造器,它接受attribute的強制性狀態(tài)消息,而且這個類可以提供公共字段和屬性,以接受attribute的可選狀態(tài)信息。這個類不應提供任何公共方法、事件或其他成員。 三、attribute的構造器和字段/屬性的數(shù)據(jù)類型 定義一個attribute類時,可定義構造器來獲取參數(shù)。開發(fā)人員在應用該attribute類型的一個實例時,必須指定這些參數(shù)。除此之外,可在自己的類型中定義非靜態(tài)公共字段和屬性,使開發(fā)人員能夠為attribute類的實例選擇恰當?shù)脑O置。 定義attribute類的實例構造器、字段和屬性時,數(shù)據(jù)類型只能限制在一個小的子集內(nèi)。具體的說,合法的數(shù)據(jù)類型只有:Boolean,Char,Byte,Sbyte,Int16,UInt16,Int32,Uint32,Int64,Uint64,Single,Double,String,Type,Object或枚舉類型。除此之外,還可使用上述任意類型的一維0基數(shù)組。然而,要盡量避免使用數(shù)組,因為對于attribute類來說,如果它的構造器要獲取一個數(shù)組作為參數(shù),就會失去與CLS的相容性。 應用一個attribute時,必須傳遞一個編譯時常量表達式,它與attribute類定義的類型相匹配。在attribute類定義一個Type參數(shù),Type字段或者Type屬性的任何地方,都必須使用C#的typeof操作符。在attribute類定義一個Object參數(shù)、Object字段或Object屬性的任何地方,都可以傳遞一個Int32、String或者其他任何常量表達式(包括null)。如果常量表達式代表一個值類型,那么在運行時構造一個attribute的實例時,會對值類型進行裝箱。以下是一個示例attribute及用法:public enum Color { Red } [AttributeUsage(AttributeTargets.All)]internal sealed class SomeAttribute : Attribute{ public SomeAttribute(String name, Object o, Type[] types) { // 'name' 引用了一個String類型 // 'o' 引用了一個合法類型(如有必要,就進行裝箱) // 'types' 引用一個一維0基Type數(shù)組 }} [Some("Jeff", Color.Red, new Type[] { typeof(Math), typeof(Console) })]internal sealed class SomeType{}邏輯上,當編譯器檢測到一個目標元素應用了一個attribute時,編譯器會調(diào)用attribute類的構造器,向它傳遞任何指定的參數(shù),從而構造attribute類的一個實例。然后,編譯器會采用增強型構造器語法所指定的值,對任何公共字段和屬性進行初始化。在構造并初始化好定制attribute類的對象之后,編譯器會將這個attribute對象序列化到目標元素的元數(shù)據(jù)表記錄項中。
提示:所謂"attribute",就是一個類的實例,它被序列化成駐留在元數(shù)據(jù)中的一個字節(jié)流。在運行時,可以對元數(shù)據(jù)中包含的字節(jié)進行反序列化,從而構造類的一個實例。實際發(fā)生的事情是:編譯器在元數(shù)據(jù)中生成創(chuàng)建attribute類的一個實例所需的信息。每個構造器參數(shù)都采取這樣的格式寫入:一個1字節(jié)長度的類型ID,后跟具體的值。在對構造器參數(shù)進行"序列化"之后,編譯器寫入字段/屬性名稱,后跟一個1字節(jié)的類型ID,再跟上具體的值,從而生成指定的每個字段和屬性的值。對于數(shù)組,會先保存數(shù)組元素的個數(shù),后跟每個單獨的元素。 四、檢測定制的attribute 可利用反射的技術來檢查attribute的存在。以后我們會完整探討這種技術。 假定你是Microsoft的員工,負責實現(xiàn)Enum的Format方法(Format方法會更根據(jù)是否有FlagsAttribute輸出不同的值),你會像下面這樣實現(xiàn)它:public static String Format(Type enumType, Object value, String format) { // 枚舉類型是否應用了FlagsAttrobute類型的一個實例 if(enumType.IsDefined(typeof(FlagsAttribute),false)){ //如果是,就執(zhí)行代碼,將值視為一個位標志枚舉類型 ... }else { // 如果不是,就執(zhí)行代碼,將值視為一個普通枚舉類型 }}上述代碼調(diào)用Type的IsDefined方法,要求系統(tǒng)查看枚舉類型的元數(shù)據(jù),檢查是否關聯(lián)了FlagsAttribute類的一個實例。如果IsDefined返回true,表面FlagsAttribute的一個實例已于枚舉類型關聯(lián),F(xiàn)ormat方法會認為值包含了一個位標志集合。如果IsDefined放回false,F(xiàn)ormat方法會認為值是一個普通的枚舉類型。
定義定制attribute時,也必須實現(xiàn)一些代碼來檢查某個目標上是否存在該attribute類的實例,然后執(zhí)行一些邏輯分支代碼。正因為能做到這一點,定制attribute才如此有用。 FCL提供了多種方式檢查一個attribute的存在。我們知道所有于CLS相容的attribute都是從System.Attribute派生的。這個類定義了三個靜態(tài)方法來獲取與一個目標關聯(lián)的attribute:IsDefined,GetCustomAttribute和GetCustomAttributes。每個方法都有幾個重載版本。| 方法名稱 | 說明 |
| IsDefined | 如果至少有一個指定的Attribute派生類的實例如目標關聯(lián),就放回true。這個方法效率很高,因為它不構造(反序列化)attribute類的任何實例 |
| GetCustomAttribute | 返回引用于目標的指定attribute類的一個實例。實例使用編譯時指定的參數(shù)、字段和屬性來構造。如果目標沒有引用任何attribute類的實例,就返回null。如果目標應用了指定attribute的多個實例,就拋出異常。方法通常用于已將AllowMultiple設為false的attribute。 |
| GetCustomAttributes | 返回一個數(shù)組,其中每個元素都是應用于目標的指定attribute類的一個實例。如果不為這個方法指定具體的attribute類,數(shù)組中包含的就是已應用的所有attribute的實例,不管它們是什么類。每個實例都使用編譯時指定的參數(shù)、字段和屬性來構造(反序列化)。如果目標沒有應用任何attribute類的實例,就返回一個空數(shù)組。該方法通常用于已將AllowMultiple設為true的attribute,或者用于列出已應用的所有attribute。 |
[assembly: CLSCompliant(true)] [Serializable] [DefaultMemberAttribute("Main")] [DebuggerDisplayAttribute("Richter", Name = "Jeff", Target = typeof(Program))] public sealed class Program { [Conditional("Debug")] [Conditional("Release")] public void DoSomething() { } public Program() { } [assembly: CLSCompliant(true)] [STAThread] public static void Main() { // 顯示應用于這個類型的attribute集 ShowAttributes(typeof(Program)); // 獲取與類型關聯(lián)的方法集 MemberInfo[] members = typeof(Program).FindMembers( MemberTypes.Constructor | MemberTypes.Method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static, Type.FilterNam
新聞熱點
疑難解答