第十五講 特徵
特徵
特徵(Attribute)是C#為組件編程引入的一個令人興奮的創新,它使得我們可以為程式的各種元素如類,結構,介面,方法等提供額外的描述性資訊。這些描述性資訊在程式碼運行時又可以被我們提取利用。我們先來看一個樣本程式:
using System;
public class AuthorAttribute: Attribute //作者特徵類
{
public AuthorAttribute(string name) { this.name = name; }
public string Name { get { return name; }}
private string name;
}
[Author("Anders Hejlsberg")]//特徵執行個體化
class MyClass {
int count;
public MyClass(int count) { this.count=count; }
public int Count { get { return count; }
}
}
class Test {
static void Main() {
Type t1= typeof(MyClass);
Type t2=typeof(AuthorAttribute);
object[] arr = t1.GetCustomAttributes(t2, true);//擷取特徵執行個體(數組)
Console.Write("The author of MyClass is :");
foreach(object o in arr) {
AuthorAttribute aa = (AuthorAttribute) o;
Console.Write(" {0} ", aa.Name);
}
Console.WriteLine();
}
}
程式首先聲明並實現了一個AuthorAttribute特徵類,特徵類必須直接或間接繼承自抽象類別System.Attribute,這往往是我們在使用特徵時的第一步。它將是我們提供描述性資訊的資料結構。值得指出的是AuthorAttribut特徵類名中的尾碼“Attrubute”是C#推薦的特徵類的命名方式,並非必須如此。
接下來在我們實現自己的類MyClass的時候,我們便採用前面的AuthorAttribute特徵類來為MyClass提供描述資訊。特徵執行個體化語句[Author("Anders Hejlsberg")]協助我們完成這一行為。這裡的中括弧“[ ]”告訴C#我們在進行特徵執行個體化。Author是AuthorAttribute類的去掉尾碼“Attrubute”的簡寫形式——C#編譯器可以自動識別,當然我們將Author換成AuthorAttribute後效果是一樣的。其中("Anders Hejlsberg")將調用AuthorAttribute特徵類的AuthorAttribute(string name) 執行個體構造器。通過這樣的特徵執行個體化語句後,我們就給我們的MyClass類提供了一個“撰寫者”(AuthorAttribute)的特徵描述。在Test測試類別中,我們便可以通過映射(下面的專題將予以詳述)來獲得我們先前設定的描述資訊。運行程式可以得到下面的結果:The author of MyClass is : Anders Hejlsberg 。
參數
特徵執行個體化時有兩種參數,一種如我們上面的由特徵類的執行個體構造器定義的參數,稱為“位置參數”。另一種是由特徵類中給定義的執行個體公有域或執行個體屬性的賦值的參數,稱為“指定參數”,看下面的特徵類的實現:
public class HelpAttribute: Attribute {
public HelpAttribute(string url) { this.url = url; }
public string Topic = null;
private string url;
private int number=-1;
public string Url { get { return url; }}
public int Number {
get { return number; }
set { number=value; }
}
}
對於這樣一個特徵類的執行個體化,我們往往採用下面的語句:[Help("http://www.ccw.com.cn",Topic="A demo class",Number=120)]。其中http://www.ccw.com.cn是位置參數。而Topic="A demo class"是指定參數,對應HelpAttribute類中的Topic公有域。Number=120也是指定參數,對應HelpAttribute類中的Number公有屬性。
位置參數和相應的特徵類的執行個體構造器緊密相關——構造器提供了什麼樣的參數構造方式,位置參數就對應什麼樣的形式。位置參數不可省略,當然如果特徵類提供了無參數的構造器,那應該另當別論。指定參數對應著特徵類的執行個體公有域或執行個體屬性,它在執行個體化的時候並非必須,可以省略。
位置參數和指定參數對它們的類型也有要求,這主要是要能保證在中括弧“[ ]”執行個體化語句中能完成各個參數的初始化。它們必須是下列類型之一:八種整數類型sbyte, short, int, long,byte, ushort, uint和ulong;字元類型char和布爾類型bool;兩種浮點類型float 和double;System.Object(即object)和System.Type(typeof操作符傳回值);枚舉類型。
AttributeUsage特徵類
AttributeUsage特徵類是C#保留的三個特徵類之一,其他兩個是條件特徵類ConditionalAttribute和廢棄特徵類ObsoleteAttribute。
AttributeUsage特徵類用來描述我們的自己實現的特徵類“怎樣描述其他類”,比如我們實現的特徵類用於描述方法,還是類,介面?另外是否允許我們的特徵類對同一個編程元素進行多次描述(比如一個類的撰寫者可能有多個)?一個類在被我們的特徵類描述後,這種描述是否能夠體現在他的繼承子類上?AttributeUsage特徵類通過三個屬性來體現上述的行為:ValidOn,AllowMultiple,Inherited。下面是特徵類的原型:
[AttributeUsage(AttributeTargets.Class)]
public class AttributeUsageAttribute: Attribute
{
public AttributeUsageAttribute(AttributeTargets validOn) {...}
public virtual bool AllowMultiple { get {...} set {...} }
public virtual bool Inherited { get {...} set {...} }
public virtual AttributeTargets ValidOn { get {...} }
}
其中AttributeTargets是一個枚舉類型,可以指定各種描述目標。需要指出的是AttributeUsage特徵類只能夠用於直接或間接繼承自System.Attribute的類,否則編譯器會報錯。
下面的例子示範了AttributeUsage特徵類的典型用法:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorAttribute: Attribute {
public AuthorAttribute(string name) {
this.name = name;
}
public string Name { get { return name; }}
private string name;
}
其中指定了AuthorAttribute特徵類只能用於描述類(AttributeTargets.Class),並且它允許用多個AuthorAttribute特徵類來描述同一個編程元素(AllowMultiple = true)。這裡並沒有指定Inherited的值,它預設為真(true),也就是允許繼承子類獲得父類的描述。實際上對於一個沒有用AuthorAttribute描述的特徵類:
class X Attribute: Attribute{ ... }
它相當於
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
class X Attribute: Attribute{ ... }
被AttributeUsage描述的特徵類,必須嚴格按照指定的描述資訊去描述其他編程元素,否則會引起編譯器報錯!
條件特徵類ConditionalAttribute定義在某些條件下才能夠產生調用的類。廢棄特徵類ObsoleteAttribute顧名思義是指在某些情況下我們不推薦使用舊有的被廢棄的類型或方法等編程元素,這時我們在他的前面加上廢棄特徵類從而可以通知編譯器,編譯器編譯時間往往會給出警告資訊。