程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [CLR via C#]18. Attribute

[CLR via C#]18. Attribute

編輯:C#入門知識

  attribute可以說是Microsoft .NET Framework提出的最具創意的技術之一了。利用attribute,可以聲明性的為自己的代碼構造添加注解,從而實現一些特殊的功能。attribute允許將定義的信息應用於幾乎每一個元數據表的記錄項。這種可擴展的元數據信息能在運行時查詢,從而動態改變代碼的執行方式。     attribute可運用於類型和成員。Microsoft采取了一種機制提供對用戶自定義的attribute的支持。這種機制叫做定制attribute。   關於自定義attribute,首先應該知道:它們只是將一些附加信息與某個目標元素關聯起來的方式。編譯器會在托管模塊中生成這些額外的信息。   CLR允許將attribute應用於可在文件的元數據中表示的幾乎所有元素。不過,最常應用attribute的還是以下定義表中的記錄項:TypeDef(類、結構、枚舉、接口和委托),MethodDef(含構造器)、ParamDef(方法參數)、FiledDef(字段)、PropertyDef(屬性)、EventDef(事件),AssemblyDef(程序集)和MouduleDef(模塊)。   具體的說,在C#中只允許將attribute應用於對以下任何一個目標元素進行定義的源代碼:程序集、模塊、類型(類、結構、枚舉、接口、委托)、字段、方法(含構造器)、方法參數、方法返回值、屬性、事件和泛型類型參數。   應用一個attribute時,C#允許用一個前綴明確指定attribute要應用於的目標元素。以下代碼展示了所有可能的前綴。在許多情況下,即使省略前綴,編譯器一樣能判斷一個attribute要應用於的目標元素。但在另一些情況下,必須指定前綴向編譯器清楚表達我們的移除。下面傾斜顯示的前綴是必須的。

[assembly: MyAttr()]         
[module: MyAttr()]           
)]             
   SomeType<[typevar: MyAttr()] T> { 
)]         
    Int32 SomeField = [: MyAttr()]        
   [method: MyAttr()]        
   )]      
      Int32 SomeParam) { )]      
   )]    
       {  : MyAttr()]        
   [field: MyAttr()]        
   [method: MyAttr()]       
     

  如前所述,attribute是類的一個實例。類必須有一個公共構造器,這樣才能創建它的實例。所以,將一個attribute應用於一個目標元素時,語法類似於調用類的某個實例構造器。除此之外,語言可能支持一些特殊的語法,允許你設置於attribute類關聯的公共字段或屬性。比如,我們將DllImport這個attribute應用於GetVersionEx方法:
[DllImport(,CharSet = CharSet.Auto, SetLastError = )]

  那麼,另外兩個"參數"是什麼?這種特殊的語法允許在DllImportAttbute對象構造好之後,設置對象的任何公共字段和屬性。在這個例子中,當DllImportAttbute對象構造好,而且將"Kernel32"傳給構造器之後,對象的公共實例字段CharSet和SetListError被分別設置為CharSet.Auto和true。用於設置字段或屬性的"參數"被稱為"命名參數"。這種參數是可選的,因為在應用attribute的一個實例時,不一定要指定命名參數。   另外,還可以將多個attribute應用於一個目標元素。將多個attribute應用一個目標元素時,attribute的順序是無關緊要的。在C#中,可將每個attribute都封閉到一對方括號中,也可以在一對方括號中封閉多個以逗號分隔的attribute。     現在我們已經知道attribute是從System.Attribute派生的一個類的實例,並知道如何應用一個attribute。接著我們來研究下如何定制attribute。假定你是Microsoft的一位員工,並負責為枚舉類型添加位標志(biit flag)支持。為此,我們,要做的第一件事情是定義一個FlagAttribute類: namespace System {     public class FlagsAttribute : System.Attribute {         public FlagsAttribute(){         }     } }   注意,FlagsAttribute類是從Attribute繼承的。所以,才使FlagsAttribute類成為符合CLS要求的一個attribute。除此之外,注意類名有一個Attribute後綴;這是為了保持於標准的相容性,但並不是必須的。最後,所有的非抽象attribute都至少要包括一個公共構造器。   提示:attribute類型是一個類,但這個類應該非常簡單。這個類應該只提供一個公共構造器,它接受attribute的強制性狀態消息,而且這個類可以提供公共字段和屬性,以接受attribute的可選狀態信息。這個類不應提供任何公共方法、事件或其他成員。     定義一個attribute類時,可定義構造器來獲取參數。開發人員在應用該attribute類型的一個實例時,必須指定這些參數。除此之外,可在自己的類型中定義非靜態公共字段和屬性,使開發人員能夠為attribute類的實例選擇恰當的設置。   定義attribute類的實例構造器、字段和屬性時,數據類型只能限制在一個小的子集內。具體的說,合法的數據類型只有:Boolean,Char,Byte,Sbyte,Int16,UInt16,Int32,Uint32,Int64,Uint64,Single,Double,String,Type,Object或枚舉類型。除此之外,還可使用上述任意類型的一維0基數組。然而,要盡量避免使用數組,因為對於attribute類來說,如果它的構造器要獲取一個數組作為參數,就會失去與CLS的相容性。   應用一個attribute時,必須傳遞一個編譯時常量表達式,它與attribute類定義的類型相匹配。在attribute類定義一個Type參數,Type字段或者Type屬性的任何地方,都必須使用C#的typeof操作符。在attribute類定義一個Object參數、Object字段或Object屬性的任何地方,都可以傳遞一個Int32、String或者其他任何常量表達式(包括null)。如果常量表達式代表一個值類型,那麼在運行時構造一個attribute的實例時,會對值類型進行裝箱。以下是一個示例attribute及用法:  
   
, Color.Red,  Type[] { (Math),   

  提示:所謂"attribute",就是一個類的實例,它被序列化成駐留在元數據中的一個字節流。在運行時,可以對元數據中包含的字節進行反序列化,從而構造類的一個實例。實際發生的事情是:編譯器在元數據中生成創建attribute類的一個實例所需的信息。每個構造器參數都采取這樣的格式寫入:一個1字節長度的類型ID,後跟具體的值。在對構造器參數進行"序列化"之後,編譯器寫入字段/屬性名稱,後跟一個1字節的類型ID,再跟上具體的值,從而生成指定的每個字段和屬性的值。對於數組,會先保存數組元素的個數,後跟每個單獨的元素。     可利用反射的技術來檢查attribute的存在。以後我們會完整探討這種技術。   假定你是Microsoft的員工,負責實現Enum的Format方法(Format方法會更根據是否有FlagsAttribute輸出不同的值),你會像下面這樣實現它:
 
    (enumType.IsDefined((FlagsAttribute),

  定義定制attribute時,也必須實現一些代碼來檢查某個目標上是否存在該attribute類的實例,然後執行一些邏輯分支代碼。正因為能做到這一點,定制attribute才如此有用。   FCL提供了多種方式檢查一個attribute的存在。我們知道所有於CLS相容的attribute都是從System.Attribute派生的。這個類定義了三個靜態方法來獲取與一個目標關聯的attribute:IsDefined,GetCustomAttribute和GetCustomAttributes。每個方法都有幾個重載版本。 方法名稱 說明 IsDefined 如果至少有一個指定的Attribute派生類的實例如目標關聯,就放回true。這個方法效率很高,因為它不構造(反序列化)attribute類的任何實例 GetCustomAttribute 返回引用於目標的指定attribute類的一個實例。實例使用編譯時指定的參數、字段和屬性來構造。如果目標沒有引用任何attribute類的實例,就返回null。如果目標應用了指定attribute的多個實例,就拋出異常。方法通常用於已將AllowMultiple設為false的attribute。 GetCustomAttributes 返回一個數組,其中每個元素都是應用於目標的指定attribute類的一個實例。如果不為這個方法指定具體的attribute類,數組中包含的就是已應用的所有attribute的實例,不管它們是什麼類。每個實例都使用編譯時指定的參數、字段和屬性來構造(反序列化)。如果目標沒有應用任何attribute類的實例,就返回一個空數組。該方法通常用於已將AllowMultiple設為true的attribute,或者用於列出已應用的所有attribute。   如果只想知道一個attribute是否應用於一個目標,那麼應該調用IsDefined,因為它的效率比另外兩個方法高的多。我們知道,將一個attribute應用於一個目標時,可以為attribute的構造器指定參數,並可以選擇設置字段或屬性。使用IsDefined不會構造一個attribute對象,不會調用它的構造器,也不會設置它的字段和屬性。   每次使用GetCustomAttribute和GetCustomAttributes方法時,都會為構造attribute對象的新實例,並根據源代碼中指定的值來設置每個實例的字段和屬性。這個兩個方法返回的都是一個引用,執行完全構造好的attribute類的實例。   調用上述任何一個方法時,它們內部必須掃描托管模塊的元數據,執行字符串比較來定位指定的attribute類。顯然,這些操作會耗費一定的事件。假如對性能要求高,可以考慮緩存這些方法調用的返回結果。   System.Reflection 命名空間定義了幾個類允許你檢查一個模塊的元數據的內容。這些類包括Assmbly,Module,ParameterInfo,MemberInfo,Type,MethodInfo,ConstructorInfo,FiledInfo,EeventInfo,PropertyInfo及其各自的*Builder類。所以這些方法還提供了IsDefined和GetCustomAttributes方法。只有System.Attribute提供了非常方便的GetCustomAttribute方法。   反射類提供的那個版本的GetCustomAttributes方法返回的是有Object實例構成的一個數據(Object[]),而不是由Attribute實例構成的一個數組(Attribute[])。   注意:將一個類傳給IsDefined,GetCustomAttribute或GetCustomAttributes方法時,這些方法會搜索指定的attribute類或它的派生類,如果代碼要搜索一個具體的attribute類,應該針對返回值執行一個額外的檢查,確保這些方法返回的正是向搜索的莪累。還可以考慮將自己的attribute類定義成sealed,減少可能存在的混淆,並避免這個檢查。     以下示例代碼列出了一個類型中定義的所有方法,並顯示應用於每個方法的attribute代碼。  
[assembly: CLSCompliant(, Name = , Target =      
            ShowAttributes(
            MemberInfo[] members = || BindingFlags.Instance | BindingFlags.Public | (MemberInfo member 
  ===  ?  (Attribute attribute 
                Console.WriteLine( (attribute  (attribute  (attribute = attribute  (dda != 

Attributes applied to Program:   System.SerializableAttribute   System.Diagnostics.DebuggerDisplayAttribute     Value=Richter, Name=Jeff, Target=ConsoleTest.Program   System.Reflection.DefaultMemberAttribute     MemberName=Main   Attributes applied to DoSomething:   System.Diagnostics.ConditionalAttribute     ConditionString=Debug   System.Diagnostics.ConditionalAttribute     ConditionString=Release   Attributes applied to Main:   System.STAThreadAttribute   Attributes applied to .ctor: None     現在,我們的代碼能判斷是否將一個attribute的實例應用於一個目標了。除此之外,可能還需要檢查attribute的字段來確定它們的值。為此,一個方法就是老老實實地寫代碼來檢查attribute類的字段的值。System.Attribute重寫了Object的Equals方法。這個方法內部會比較兩個對象的類型。如果 不一致,Equals會返回false。如果類型一致,Equals會利用反射來比較兩個attribute對象中的字段值(為每個字段調用Equals)。如果所有字段都匹配,就返回true;否則返回false。但我們可以在自己attribute類中重寫Equals來移除反射的使用,從而提升性能。   System.Attribute還公開了虛方法Match,可重寫它來提供更豐富的語義。Match的默認實現只是調用的Equals方法並返回它的結果。下面演示了如何重寫Equals和Match,後者在一個attribute代碼另一個attribute的子集的前提下返回true。另外,還演示了如何使用Match。
 = = =   = 
 
             (obj ==   (.GetType() != =
             ((other.m_accounts & m_accounts) !=  ;   
 
 
             (obj ==   (.GetType() != =
             (other.m_accounts !=  ;   

           | Accounts.Checking |     
            CanWriteCheck(  
            Attribute checking = 
            Attribute validAccounts =(AccountsAttribute), 
             ((validAccounts != ) &&

ConsoleTest.ChildAccount types can NOT write checks. ConsoleTest.AdultAccount types can write checks. ConsoleTest.Program types can NOT write checks.     本節將討論如何利用另一種技術來檢測應用於一個元數據記錄項的attribute。在某些安全性要求嚴格的場合,這個技術能保證不會執行從Attribute派生的類中的代碼。畢竟,調用Attribute的GetCustomAttribute或者GetCustomAttributes方法時,這些方法會在內部調用attribute類的構造器,而且可能調用屬性的set方法。除此之外,首次訪問一個類型會造成CLR調用類型的類型構造器(如果有的化話)。在構造器、set構造器方法以及類型構造器中,可能包含每次查找一個attribute時都要執行的代碼,這樣一來,就相當於允許未知的代碼在APPDomain中運行,可以說是一個安全隱患。   使用System.Reflection.CustomAttributeData類,可以在查找attribute時同時禁止執行attribute類中的代碼。這個類定義了一個靜態方法GetCustomAttributes來獲取與一個目標關聯的attribute。該方法有4個重載版本:一個接受一個Assembly,一個接受一個Module,一個接受一個ParameterInfo,還有一個接受一個Memberinfo。這個類是在System.Reflection命名空間定義的。通常,是先用Assembly的靜態方法ReflectionOnlyLoad加載一個程序集,在用CustomAttributeData類分析這個程序集的元數據中的attribute。簡單的說,ReflectionOnlyLoad以一種特方式加載程序集,期間會禁止CLR執行程序集中的任何代碼,包括類型構造器。   CustomAttributeData的GetCustomAttributes方法相當於一個工廠方法。也就是說,調用它會返回IList<CustomAttributeData>類型的對象,其中包括了一個有CustomAttributeData構成的一個集合。在集合中,應用於指定目標的每個定制attribute都有一個對象的元素。針對每個CustomAttribute對象都可以查詢一些只讀屬性,判斷attribute對象是如何構造和初始化的。具體的說,Customctor屬性指出構造器方法"要"如何調用。ComstructorArguments屬性以一個IList<CustomAttributeTypedArgument>實例的形式返回"要"傳給這個構造器的實參。NamedArguments屬性以一個IList<CustomAttributeNamedArgument>實例的形式,返回"要"設置的字段或屬性。注意,這裡之所以說"要",是因為不會實際地調用構造器和set訪問器方法。通過禁止執行attribute類的任何方法,我們獲得了增強的安全性。     如果一個attribute類應用了System.Diagnostics.ConditionalAttribute,就稱為條件attribute類。下面是一個例子:
 VERIFY
      (Program),  (CondAttribute)) ?  : 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved