← 筆記

C#筆記 – 定制特性

  • 「自定義特性」只是將一些附加信息與某個目標元素關聯起來的方式
    • 編譯器在托管模塊的元數據中生成這些額外的信息
    • 最常應用特性的記錄項:
      • TypeDef
      • MethodDef
      • ParamDef
      • FieldDef
      • PropertyDef
      • EventDef
      • AssemblyDef
      • ModuleDef
  • 「自定義特性」實際上是一個「類型的實例」,派生自System.Attribute
    • 而「類」必須有公共構造器才能創建它的實例

      • 將特性應用於目標元素時,類似於調用其實例構造器
        • 應用特性時,向其「構造器」傳遞的參數為:「定位參數」
        • 該參數是強制性必須指定的
      • 另外也可以設置與特性類關聯的公共字段/屬性
        • 用於設置這些字段/屬性的參數為:「命名參數」
        • 不一定要指定
    •   [DllImport("TestDLL", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern void TestMethod();      
      • “TestDLL” 為「定位參數」;CharSet = CharSet.Auto和SetLastError = true為「命名參數」
自定義特性類
  • 特性類必須繼承自Attribute

    •   public class MyAttribute : Attribute{}
    • 應把特性類想成一個「邏輯狀態容器」

      • 類的內容很簡單
      • 應只提供一個公共構造器來接受特性的「定位參數」
      • 可以提供公共字段和屬性來接受「命名參數」
      • 不應提供任何公共方法、事件等其他成員
    • 如果要讓一個特性向編譯器指定它的合法應用範圍,需要向特性類應用System.AttributeUsageAttribute特性

      • 由於特性本質上是一個類的實例,因此,其作為一個類本身也可以應用其他特性
      •   [AttributeUsage(AttributeTargets.Enum, Inherited = false)]
          public class MyAttribute : Attribute{}
  • 特性構造器可接受的字段/屬性數據類型都是一些簡單的數據類型,如

    • bool/char/byte/sbyte/int/uint/single/double/string/type/object/enum
    • 可使用上述類型的SZ數組,但應盡量避免使用。如果獲取數組作為參數,將會失去與CLS的相容性
    • 應用特性時必須傳遞一個編譯時的常量表達式,它與特性類定義的類型匹配
  • 當編譯器檢測到向目標元素應用了定制特性後,會調用特性的類構造器,向它傳遞任何指定的參數,從而構造特性類的實例

    • 然後,編譯器採用增強型構造器語法所指定的值,對任何公共字段/屬性進行初始化

    • 最後,編譯器將其狀態序列化到目標元素的元數據表記錄項中

    •   [MyAttribute]
        public enum TestAttribute
        {
            None
        }
        
        [AttributeUsage(AttributeTargets.Enum)]
        public class MyAttribute : Attribute
        {
            public MyAttribute(){}
        }
    •   TypeDef #1 (02000002)
        -------------------------------------------------------
        TypDefName: CLR_Ch18.TestAttribute  (02000002)
        Flags     : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass]  (00000101)
        Extends   : 0100000C [TypeRef] System.Enum
        Field #1 (04000001)
        -------------------------------------------------------
        Field Name: value__ (04000001)
        Flags     : [Public] [SpecialName] [RTSpecialName]  (00000606)
        CallCnvntn: [FIELD]
        Field type:  I4
        
        Field #2 (04000002)
        -------------------------------------------------------
        Field Name: None (04000002)
        Flags     : [Public] [Static] [Literal] [HasDefault]  (00008056)
        DefltValue: (I4) 0
        CallCnvntn: [FIELD]
        Field type:  ValueClass CLR_Ch18.TestAttribute
        
        CustomAttribute #1 (0c00000b)
        -------------------------------------------------------
        CustomAttribute Type: 06000004
        CustomAttributeName: CLR_Ch18.MyAttribute :: instance void .ctor()
        Length: 4
        Value : 01 00 00 00                                      >                <
        ctor args: ()
  • 理解定制特性

    • 它是類的實例,被序列化成駐留在數據中的字節流
    • 運行時可對元數據中的字節進行反序列化,構造出特性類的實例
    • 實際發生:
      • 編譯器在元數據中生成創建特性類的實例所需的信息
      • 每個構造器參數都是1字節的類型ID,後跟具體的值
      • 對構造器進行「序列化」時,編譯器先寫入字段/屬性名,再跟上1字節的類型ID,最後是具體的值
        • 如果是數組,則會先保存數組元素的個數,再跟上每個單獨的元素
自定義特性的檢測
  • 通過「反射」來檢測
    • IsDefined
      •   [MyAttribute]
          public enum TestAttribute
          {
              None
          }
          
          class Program
          {
              static TestAttribute attr = TestAttribute.None; 
              public static void TestMethod_A()
              {
                  if(attr.GetType().IsDefined(typeof(MyAttribute), false))
                  {
                      Console.WriteLine("Is MyAttribute");
                  }
                  else
                  {
                      Console.WriteLine("Not MyAttribute");
                  }
              }
          }        
          
          [AttributeUsage(AttributeTargets.Enum)]
          public class MyAttribute : Attribute
          {
              public MyAttribute(){}
          }
      • IsDefined要求系統查看枚舉類型的元數據,檢查是否關聯了對象特性類的實例

      • 只判斷目標是否應用了一個特性,使用時,不會調用特性對象的構造器或設置其包含的字段/屬性

    • GetCustomAttribute(s)
      • 會構造指定特性類型的新實例,其返回對該特性實例的引用
  • 無論是調用以上哪個方法,內部都須掃描托管模塊的元數據,執行字符串比較來定位指定的定制特性類
匹配特性實例
  • System.Attribute雖然重寫了Object的Equals方法,但是比較低效
    • 首先會在內部比較兩個對象的類型
    • 然後會使用反射比較兩個特性對象中每個字段的值
  • System.Attribute還公開了虛方法Match讓我們重寫

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter