「自定義特性」只是將一些附加信息與某個目標元素關聯起來的方式
編譯器在托管模塊的元數據中生成這些額外的信息
最常應用特性的記錄項:
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