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)
- 會構造指定特性類型的新實例,其返回對該特性實例的引用
- IsDefined
- 無論是調用以上哪個方法,內部都須掃描托管模塊的元數據,執行字符串比較來定位指定的定制特性類
匹配特性實例
- System.Attribute雖然重寫了Object的Equals方法,但是比較低效
- 首先會在內部比較兩個對象的類型
- 然後會使用反射比較兩個特性對象中每個字段的值
- System.Attribute還公開了虛方法Match讓我們重寫
參考書目
- 《CLR via C#》(第4版) Jeffrey Richter