特性是一種允許我們向程序集增加元數據的語言結構,它是用於保存程序結構信息的某種特殊類型的類。
根據慣例,特性名使用Pascal命名法並且以Attribute
后綴結尾。當為目標應用特性時,我們可以不使用后綴。例如對於SerializableAttribute
和MyAttributeAttribute
這兩個特性,我們在把他們應用到結構時可以使用Serializable
和MyAttribute
短名稱。
所有特性類都派生自System.Attribute
,用戶自定義的特殊類叫做自定義特性。
聲明自定義特性
- 派生自
System.Attribute
- 起一個以后綴為
Attribute
結尾的名字
為安全起見,建議聲明一個sealed
的特性類
- 由於特性持有目標的信息,所以特性類的公共成員只能是:字段,屬性,構造函數。
使用特性的構造函數
和其他類一樣,都有構造函數,每一個特性至少必須有一個公共構造函數,如果不聲明構造函數,編譯器會為我們產生一個隱式,公共且無參的構造函數,也可以被重載,聲明構造函數時,必須使用類全名(即包括后綴)。在應用時,才可以使用短名稱(不包括后綴)。
[MyAttribute("Holds a value")] //使用了一個字符串的構造函數,它只是聲明語句,只有特性的消費者訪問特性時候才能調用構造函數,它不會決定什么時候構造特性類的對象。
public int MyField;
構造函數中的位置參數和命名參數
[MyAttribute("An excellent class",Review="Amy",ver="0.7.1")]
第一個參數是位置參數,后兩個是命名參數。
public sealed class MyAttributeAttribute:System.Attribute
{
public string Description;
public string Ver;
public string Reviewer;
public MyAttributeAttribute(string desc){
Description=desc;
}
}
[MyAttribute("Excellent class",Reviewer="CJ266",Ver="0.7.1")] //雖然構造函數只有一個形參,但我們可以通過命名參數給構造函數3個實參,這與普通的類是不一樣的。
class MyClass{
}
上述代碼表示,構造函數的聲明只列出一個形參,但我們可以通過命名參數給構造函數3個,但需要注意的是,構造函數需要的任何位置參數都必須放在命名參數之前。
限制特性
特性本身就是類,有一個很重要的預定義特性可以應用到自定義特性上,那就是AttributeUsage
特性,可以用它來限制特性使用在某個目標類型上。
例如,如果我們希望自定義特性MyAttribute
只應用到方法上,那么可以以如下方式使用AttributeUsage
:
[AttributeUsage(AttributeTarget.Method)]
public sealed class MyAttributeAttribute:System.Attribute{...}
AttributeUsage
有三個重要的公共屬性:
名字 | 意義 | 默認值 |
---|---|---|
ValidOn |
限制特性能應用的目標類型的列表,構造函數的第一個參數必須是AttributeTarget 類型的枚舉值 |
|
Inherited |
一個布爾值,指示特性是否會被裝飾類型的派生類所繼承 | true |
AllowMultiple |
一個指示目標是否被應用多個特性的實例的布爾值 | false |
AttributeTarget
的枚舉值成員:
All |
Assembly |
Class |
Constructor |
---|---|---|---|
Delegate |
Enum |
Event |
Field |
GenericParameter |
Interface |
Method |
Module |
Parameter |
Property |
ReturnValue |
Struct |
在使用AttributeUsage
時,構造函數至少需要一個參數,參數包含的目標類型會保存在ValidOn
中,還可以通過命名參數有選擇地設置Inherited
和AllowMultiple
屬性。
訪問特性
我們可以通過Type
對象獲取了解有關類型的幾乎所有信息:
成員 | 成員類型 | 描述 |
---|---|---|
Name |
屬性 | 返回類型的名字 |
Namespace |
屬性 | 返回包含類型聲明的命名空間 |
Assembly |
屬性 | 返回聲明類型的程序集,如果類型是泛型的,返回定義這個類型的程序集 |
GetFields |
方法 | 返回類型的字段列表 |
GetProperties |
方法 | 返回類型的屬性列表 |
GetMethods |
方法 | 返回類型的方法列表 |
對於訪問自定義特性來說,我們也可以用Type
的兩個方法(IsDefined
和GetCustomAttributes
)
IsDefined
方法
使用IsDefined
方法來判斷特性是否應用到了,第一個參數是接受需要檢查特性的Type
對象,第二個參數是bool
類型,指示是否搜索繼承樹來查找這個特性。
GetCustomAttributes
方法
該方法返回的對象是object
的數組,因此我們必須強制轉換為相應的特性類型,布爾參數指定是否搜索繼承樹來查找特性。
[AttributeUsage(AttributeTargets.Class)]
public sealed class ReviewCommentAttribute : System.Attribute
{
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;
}
}
class BaseClass
{
public int BaseField = 0;
}
[ReviewComment("This is Derived","0.8.1")]
class DerivedClass : BaseClass
{
public int DerivedField = 0;
}
class Program1
{
static void Main()
{
var bc = new BaseClass();
var dc = new DerivedClass();
BaseClass[] bca = new BaseClass[] { dc, bc };
foreach (var v in bca)
{
Console.WriteLine("object type:{0}", v.GetType().Name);
var fi = v.GetType().GetFields();
Console.WriteLine($"IsDefined:{v.GetType().Name}:{v.GetType().IsDefined(typeof(ReviewCommentAttribute), false)}");
foreach (var f in fi)
{
Console.WriteLine("Field:{0}", f.Name);
}
}
var t = dc.GetType();
var Attrs = t.GetCustomAttributes(true);
foreach (var attr in Attrs)
{
var attr1 = attr as ReviewCommentAttribute;
Console.WriteLine($"{attr1.Description}");
}
}
}