自定義特性
你或許已經注意到了,應用特性的語法和之前見過的其他語法有很大不同。你可能會覺得特
性是一種完全不同的結構類型,其實不是,特性只是一種特殊的類。
有關特性類的一些要點如下。
- 用戶自定義的特性類叫作自定義特性。
- 所有特性類都派生自System.Attribute。
聲明自定義特性
總體來說,聲明一個特性類和聲明其他類一樣。然而,有一些事項值得注意,如下所示。
- 要聲明一個自定義特性,需要做如下工作。
- 聲明一個派生自System.Attribute的類。
- 給它起一個以后綴Attribute結尾的名字。
- 安全起見,通常建議你聲明一個sealed的特性類。
例如,下面的代碼顯示了MyAttributeAttribute特性的聲明的開始部分:
由于特性持有目標的信息,所有特性類的公有成員只能是:
- 字段
- 屬性
- 構造函數
使用特性的構造函數
特性和其他類一樣,有構造函數。每一個特性必須至少有一個公共構造函數。
- 和其他類一樣,如果你不聲明構造函數,編譯器會為你產生一個隱式、公共且無參的構
造函數。 - 特性的構造函數和其他構造函數一樣,可以被重載。
- 聲明構造函數時必須使用類全名,包括后綴。只可以在應用特性時使用短名稱。
例如,如果有如下的構造函數(名字沒有包含后綴),編譯器會產生一個錯誤消息
public MyAttributeAttribute(string desc,string ver)
{Description = desc;VersionNumber = ver;
}
指定構造函數
當我們為目標應用特性時,其實是在指定應該使用哪個構造函數來創建特性的實例。列在特
性應用中的參數其實就是構造函數的實參。
例如,在下面的代碼中,MyAttribute被應用到一個字段和一個方法上。對于字段,聲明指定
了使用帶單個字符串參數的構造函數。對于方法,聲明指定了使用帶兩個字符串參數的構造函數。
[MyAttribute("Holds a value")] //使用一個字符串的構造函數
public int MyField;[MyAttribute("Version 1.3", "Galen Daniel")] //使用兩個字符串的構造函數
public void MyMethod()
{...
}
特性構造函數的其他要點如下。
- 在應用特性時,構造函數的實參必須是在編譯時能確定值的常量表達式。
- 如果應用的特性構造函數沒有參數,可以省略圓括號。例如,如下代碼中的兩個類都使
用MyAttr特性的無參構造函數。兩種形式的意義是相同的。
[MyAttr]
class SomeClass...[MyAttr]
class OtherClass...
使用構造函數
和其他類一樣,我們不能顯式調用構造函數。特性的實例被創建后,只有特性的消費者訪問
特性時才能調用構造函數。這一點與其他類的實例不同,這些實例都創建在使用對象創建表達式
的位置。應用一個特性是一條聲明語句,它不會決定什么時候構造特性類的對象。
圖25-4比較了普通類構造函數的使用和特性的構造函數的使用。
- 命令語句的實際意義是:“在這里創建新的類。
- 聲明語句的意義是:“這個特性和這個目標相關聯,如果需要構造特性,則使用這個構造
函數。
構造函數中的位置參數和命名參數
與普通類的方法和構造函數相似,特性的構造函數同樣可以使用位置參數和命名參數。
如下代碼顯示了使用一個位置參數和兩個命名參數來應用一個特性:
[MyAttribute("An execellent class",Reviewer="Amy McArthur",Ver="0.7.15.33)]
下面的代碼演示了特性類的聲明以及為MyClass類應用特性。注意,構造函數的聲明只列出了
一個形參,但我們可通過命名參數給構造函數3個實參。兩個命名參數設置了字段ver和Reviewer
的值。
public sealed class MyAttributeAttribute:System.Attribute
{public string Description;public string Ver;public string Reviewer;public MyAttributeAttribute(string desc) //一個形參{Description=desc;}
}[MyAttribute("An execellent class",Reviewer="Amy McArthur",Ver="7.15.33")]
class MyClass
{...
}
說明 和方法一樣,構造函數需要的任何位置參數都必須放在命名參數之前。
限制特性的使用
我們已經看到了可以為類應用特性。但特性本身就是類,有一個很重要的預定義特性可以應
用到自定義特性上,那就是AttributeUsage特性。我們可以使用它來限制將特性用在某個目標類
例如,如果希望自定義特性MyAttribute只能應用到方法上,那么可以以如下形式使用
AttributeUsage:
[AttributeUsage(AttributeUsage.Method)]
public sealed class MyAttributeAttribute:System.Attribute
{...
}
AttributeUsage有3個重要的公有屬性,如表25-4所示。表中顯示了屬性名和屬性的含義。
對于后兩個屬性,還顯示了它們的默認值。
名 字 | 定 義 | 默 認 值 |
---|---|---|
ValidOn | 保存能應用特性的目標類型列表。構造函數的第一個參數必須是 AttributeTargets 類型的枚舉值 | |
Inherited | 一個布爾值,它指示特性是否可被裝飾類型的派生類所繼承 | true |
AllowMultiple | 一個布爾值,指示目標上是否可應用特性的多個實例 | false |
AttributeUsage的構造函數
AttributeUsage的構造函數接受單個位置參數,該參數指定了可使用特性的目標類型。它用這
個參數來設置Valid0n屬性,可接受的目標類型是AttributeTargets枚舉的成員。AttributeTargets
枚舉的完整成員列表如表25-5所示。
可以通過使用按位或運算符來組合使用類型。例如,在下面的代碼中,被裝飾的特性只能應
用到方法和構造函數上。
[AttributeUsage(AttributeTarget.Method|AttributeTarget.Constructor)]
public sealed class MyAttributeAttribute:System.Attribute
{...
}
All | Assembly | Class | Constructor |
---|---|---|---|
Delegate | Enum | Event | Field |
GenericParameter | Interface | Method | Module |
Parameter | Property | ReturnValue | Struct |
當為特性聲明應用AttributeUsage時,構造函數至少需要一個必需的參數,參數包含的目標
類型會保存在Valid0n中。還可以通過使用命名參數有選擇地設置lnherited和AllowMu1tiple
屬性。如果不設置,它們會保持如表25-4所示的默認值。
作為示例,下面一段代碼指定了MyAttribute的如下方面。
- MyAttlibute能且只能應用到類上。
- MyAttribute不會被應用它的類的派生類所繼承。
- 不能在同一個目標上應用MyAttribute的多個實例。
[AttributeUsage(AttributeTarget.Class,//必需的位置參數Inherited=false, //可選的命名參數AllowMultiple=false)] //可選的命名參數public sealed class MyAttributeAttribute:System.Attribute
{...
}
自定義特性的最佳實踐
強烈推薦編寫自定義特性時參考如下實踐。
- 特性類應該表示目標結構的某種狀態。
- 如果特性需要某些字段,可以通過包含具有位置參數的構造函數來收集數據,可選字段
可以采用命名參數按需初始化。 - 除了屬性之外,不要實現公有方法或其他函數成員。
- 為了更安全,把特性類聲明為sealed。
- 在特性聲明中使用AttributeUsage來顯式指定特性目標組。
如下代碼演示了這些準則:
[AttributeUsage(AttributeTargets.Class)]
public sealed class ReviewCommentAttribute:System.AttributeTargets
{public string Description{get;set;}public string VersionNumber{get;set;}public string ReviewerID{get;set;}public ReviewCommentAttribute(string desc,string ver){Description=desc;VersionNumber=ver;}
}