特性

请明月發表於2024-10-18

特性

公共語言執行時使你能夠新增類似於關鍵字的描述性宣告(稱為特性),特性是後設資料的一種應用,也是作為描述資料的資料,以便批註程式設計元素(如型別、欄位、方法和屬性)。編譯執行時的程式碼時,它將被轉換為 Microsoft 中間語言 (MSIL),並和編譯器生成的後設資料一起放置在可移植可執行 (PE) 檔案內。 特性使你能夠將額外的描述性資訊放到可使用執行時反射服務提取的後設資料中。 當你宣告派生自 System.Attribute 的特殊類的例項時,編譯器會建立特性。

.NET 出於多種原因且為解決許多問題而使用特性。 特性描述如何將資料序列化、指定用於強制安全性的特徵並限制透過實時 (JIT) 編譯器進行最佳化,從而使程式碼易於除錯。 特性還可記錄檔案的名稱或程式碼的作者,或控制窗體開發過程中控制元件和成員的可見性。

應用屬性

編譯程式碼時,特性將被髮到後設資料中,並且透過執行時反射服務可用於公共語言執行時和任何自定義工具或應用程式。

按照慣例,所有特性名稱都以“Attribute”結尾。 但是,面向執行時的幾種語言(如 Visual Basic 和 C#)無需指定特性的全名。 例如,若要初始化 System.ObsoleteAttribute,只需將它引用為 Obsolete 即可。

將特性應用於方法

以下程式碼示例顯示如何使用 System.ObsoleteAttribute(其將程式碼標記為已過時)。 將字串 "Will be removed in next version" 傳遞給特性。 當特性描述的程式碼被呼叫時,此特性會導致產生編譯器警告,顯示傳遞的字串。

public class Example
{
    // Specify attributes between square brackets in C#.
    // This attribute is applied only to the Add method.
    [Obsolete("Will be removed in next version.")]
    public static int Add(int a, int b)
    {
        return (a + b);
    }
}

class Test
{
    public static void Main()
    {
        // This generates a compile-time warning.
        int i = Example.Add(2, 2);
    }
}

在程式集級別應用特性

如果要在程式集級別應用屬性,請使用 assemblyAssembly(Visual Basic 中用assembly )關鍵字。 下列程式碼顯示在程式集級別應用的 AssemblyTitleAttribute。

using System.Reflection;
[assembly:AssemblyTitle("My Assembly")]

應用此特性時,字串 "My Assembly" 將被放置在檔案後設資料部分的程式集清單中。


編寫自定義屬性

自定義屬性直接或間接派生自 System.Attribute 類的傳統類。 與傳統類一樣,自定義特性包含用於儲存和檢索資料的方法。
正確設計自定義特性的主要步驟如下:

  1. 應用 AttributeUsageAttribute
  1. 宣告特性類
  2. 宣告建構函式
  3. 宣告屬性

應用 AttributeUsageAttribute

自定義屬性宣告以 System.AttributeUsageAttribute 屬性開頭,定義特性類的一些主要特徵。 例如,你可以指定其他類是否可以繼承你的屬性,或者此屬性可以應用到哪些元素。 以下程式碼片段演示 AttributeUsageAttribute 的使用方法:

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]

AttributeUsageAttribute 包含下列三個成員,它們對建立自定義屬性非常重要:AttributeTargets、Inherited 和 AllowMultiple。

AttributeTargets 成員

上述示例中指定 AttributeTargets.All,表示此屬性可應用於所有程式元素。 或者,你可指定 AttributeTargets.Class 和 AttributeTargets.Method,前者表示你的特性僅可適用於一個類,後者表示你的特性僅可應用於一種方法。 所有程式元素都可以透過這種方式使用自定義特性來標記,以對其進行描述。還可傳遞多個 AttributeTargets 值。 以下程式碼段指定可以將自定義屬性應用於任何類或方法:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 

Inherited 屬性

AttributeUsageAttribute.Inherited 屬性指明要對其應用屬性的類的派生類能否繼承此屬性。 此屬性使用 true(預設值)或 false 標誌。 在以下示例中,MyAttribute 的預設 Inherited 值為 true,而 YourAttribute 的 Inherited 值為 false:

// This defaults to Inherited = true.
public class MyAttribute : Attribute
{
    //...
}

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class YourAttribute : Attribute
{
    //...
}

AllowMultiple 屬性

AttributeUsageAttribute.AllowMultiple 屬性指明元素能否包含屬性的多個例項。 如果設定為 true,則允許多個例項。 如果設定為 false(預設值),那麼只允許一個例項。

在以下示例中,MyAttribute 的預設 AllowMultiple 值為 false,而 YourAttribute 的值為 true:

//This defaults to AllowMultiple = false.
public class MyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class YourAttribute : Attribute
{
}

當應用這些特性的多個例項時, MyAttribute 會生成編譯器錯誤。 以下程式碼示例顯示 YourAttribute 的有效用法以及 MyAttribute的無效用法:

public class MyClass
{
    // This produces an error.
    // Duplicates are not allowed.
    [MyAttribute]
    [MyAttribute]
    public void MyMethod()
    {
        //...
    }

    // This is valid.
    [YourAttribute]
    [YourAttribute]
    public void YourMethod()
    {
        //...
    }
}

如果 AllowMultiple 屬性和 Inherited 屬性都設定為 true,從另一個類繼承的類可以繼承一個屬性,並具有在同一個子類中應用相同屬性的另一個例項。 如果 AllowMultiple 設定為 false,則父類中的所有特性的值將被子類中同一特性的新例項覆蓋。

宣告特性類

應用 AttributeUsageAttribute 以後,開始定義屬性的細節。 特性類的宣告類似於傳統類的宣告,如以下程式碼所示

[AttributeUsage(AttributeTargets.Method)]
public class MyAttribute : Attribute
{
    // . . .
}

此特性定義說明了以下幾點:

  • 特性類必須宣告為公共類。
  • 按照約定,特性類的名稱以單詞 Attribute結束。 儘管沒有要求,但仍建議執行此約定以保證可讀性。 應用特性時,可以選擇是否包含單詞 Attribute。
  • 所有特性類必須直接或間接從 System.Attribute 類繼承。
  • 在 Microsoft Visual Basic 中,所有自定義特性類必須具有 System.AttributeUsageAttribute 特性。

宣告建構函式

類似於傳統類,特性是透過建構函式初始化的。 下面的程式碼段闡明瞭典型的特性建構函式。 此公共建構函式採用一個引數,並設定一個等於其值的成員變數。

public MyAttribute(bool myvalue)
{
    this.myvalue = myvalue;
}

可以過載此建構函式以適應值的各種組合。 如果你還為自定義特性類定義了 屬性 ,則在初始化該特性時可以使用命名引數和定位引數的組合。 通常情況下,將所有必選的引數定義為定位引數,將所有可選的引數定義為命名引數。 在這種情況下,沒有必需的引數就無法初始化屬性。 其他所有引數都是可選引數。

宣告屬性

如果你想要定義一個命名引數,或者提供一種簡單的方法來返回由特性儲存的值,請宣告 屬性。 應將特性的屬性宣告為公共實體,此公告實體包含將返回的資料型別的描述。 定義將儲存屬性值的變數,並將此變數與 get 和 set 方法相關聯。 以下程式碼示例說明如何在屬性中實現一個簡單屬性:

public bool MyProperty
{
    get {return this.myvalue;}
    set {this.myvalue = value;}
}

自定義特性的示例

結合了前面的資訊,顯示如何設計一個屬性來記錄有關一段程式碼的作者的資訊。 本示例中的特性儲存了程式設計人員的姓名和級別,以及是否已檢查此程式碼的資訊。 它使用三個私有變數來儲存要儲存的實際值。 每個變數用獲取和設定這些值的公共屬性表示。 最後,使用兩個必需的引數定義建構函式:

[AttributeUsage(AttributeTargets.All)]
public class DeveloperAttribute : Attribute
{
// Private fields.
private string name;
private string level;
private bool reviewed;

// This constructor defines two required parameters: name and level.

public DeveloperAttribute(string name, string level)
{
    this.name = name;
    this.level = level;
    this.reviewed = false;
}

// Define Name property.
// This is a read-only attribute.

public virtual string Name
{
    get {return name;}
}

// Define Level property.
// This is a read-only attribute.

public virtual string Level
{
    get {return level;}
}

// Define Reviewed property.
// This is a read/write attribute.

public virtual bool Reviewed
{
    get {return reviewed;}
    set {reviewed = value;}
}

}

可以採用以下任一種方法,使用全稱 DeveloperAttribute 或縮寫名稱 Developer 應用此屬性:

[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]

檢索儲存在特性中的資訊

檢索自定義屬性的過程非常簡單。 首先,宣告要檢索的屬性例項。 然後,使用 Attribute.GetCustomAttribute 方法,用要檢索的屬性的值初始化新屬性。 在初始化新特性後,可使用它的屬性來獲取值。

檢索一個屬性例項

在下面的示例中,DeveloperAttribute(如上一部分所述)在類一級適用於 MainApp 類。 GetAttribute 方法使用 GetCustomAttribute 在類級別檢索 DeveloperAttribute 中儲存的值,再在控制檯中顯示它們。

using System;
using System.Reflection;
using CustomCodeAttributes;

[Developer("Joan Smith", "42", Reviewed = true)]
class MainApp
{
    public static void Main()
    {
        // Call function to get and display the attribute.
        GetAttribute(typeof(MainApp));
    }

    public static void GetAttribute(Type t)
    {
        // Get instance of the attribute.
        DeveloperAttribute MyAttribute =
            (DeveloperAttribute) Attribute.GetCustomAttribute(t, typeof (DeveloperAttribute));

        if (MyAttribute == null)
        {
            Console.WriteLine("The attribute was not found.");
        }
        else
        {
            // Get the Name value.
            Console.WriteLine("The Name Attribute is: {0}." , MyAttribute.Name);
            // Get the Level value.
            Console.WriteLine("The Level Attribute is: {0}." , MyAttribute.Level);
            // Get the Reviewed value.
            Console.WriteLine("The Reviewed Attribute is: {0}." , MyAttribute.Reviewed);
        }
    }
}

//output:
//The Name Attribute is: Joan Smith.  
//The Level Attribute is: 42.  
//The Reviewed Attribute is: True.  

如果找不到特性,GetCustomAttribute 方法會將 MyAttribute 初始化為 null 值。 此示例在 MyAttribute 中查詢此類例項,並在找不到特性時通知使用者。 如果在類範圍中找不到 DeveloperAttribute,控制檯會顯示以下訊息:

The attribute was not found.

檢索應用於同一範圍的多個屬性例項

在上一示例中,要檢查的類和要查詢的特定特性都傳遞給 GetCustomAttribute 方法。 此程式碼非常適用於只有一個屬性例項在類一級應用的情況。 不過,如果在相同類級別應用一個特性的多個例項,GetCustomAttribute 方法不會檢索所有資訊。 如果同一特性的多個例項應用於相同範圍,可以使用 Attribute.GetCustomAttributes 方法將特性的所有例項都放入一個陣列中。 例如,如果在相同的類一級應用兩個 DeveloperAttribute 例項,可以將 GetAttribute 方法修改為顯示在這兩個屬性中找到的資訊。 請記住,在同一級別應用多個特性。 必須在 AttributeUsageAttribute 類中將 AllowMultiple 屬性設定為 true 來定義該特性。

下面的程式碼示例展示瞭如何使用 GetCustomAttributes 方法來建立陣列,以引用任何給定類中的所有 DeveloperAttribute 例項。 然後,程式碼會將所有特性的值輸出到控制檯。

public static void GetAttribute(Type t)
{
    DeveloperAttribute[] MyAttributes =
        (DeveloperAttribute[]) Attribute.GetCustomAttributes(t, typeof (DeveloperAttribute));

    if (MyAttributes.Length == 0)
    {
        Console.WriteLine("The attribute was not found.");
    }
    else
    {
        for (int i = 0 ; i < MyAttributes.Length ; i++)
        {
            // Get the Name value.
            Console.WriteLine("The Name Attribute is: {0}." , MyAttributes[i].Name);
            // Get the Level value.
            Console.WriteLine("The Level Attribute is: {0}." , MyAttributes[i].Level);
            // Get the Reviewed value.
            Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttributes[i].Reviewed);
        }
    }
}

如果找不到任何屬性,此程式碼會向使用者發出警報。 如果找到,就會顯示兩個 DeveloperAttribute 例項中包含的資訊。

檢索應用於不同範圍的多個屬性例項

GetCustomAttributes 和 GetCustomAttribute 方法不會搜尋整個類,也不會返回該類中特性的所有例項。 而是一次只搜尋一個指定方法或成員。 如果你有一個類將同一特性應用於每個成員,並且你需要檢索應用於這些成員的所有特性的值,則必須將各個方法或成員單獨提供給 GetCustomAttributes 和 GetCustomAttribute。

下面的程式碼示例將一個類用作引數,並在類級別以及該類的每一個方法上搜尋 DeveloperAttribute(如前面所定義):

public static void GetAttribute(Type t)
{
    DeveloperAttribute att;

    // Get the class-level attributes.

    // Put the instance of the attribute on the class level in the att object.
    att = (DeveloperAttribute) Attribute.GetCustomAttribute (t, typeof (DeveloperAttribute));

    if (att == null)
    {
        Console.WriteLine("No attribute in class {0}.\n", t.ToString());
    }
    else
    {
        Console.WriteLine("The Name Attribute on the class level is: {0}.", att.Name);
        Console.WriteLine("The Level Attribute on the class level is: {0}.", att.Level);
        Console.WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att.Reviewed);
    }

    // Get the method-level attributes.

    // Get all methods in this class, and put them
    // in an array of System.Reflection.MemberInfo objects.
    MemberInfo[] MyMemberInfo = t.GetMethods();

    // Loop through all methods in this class that are in the
    // MyMemberInfo array.
    for (int i = 0; i < MyMemberInfo.Length; i++)
    {
        att = (DeveloperAttribute) Attribute.GetCustomAttribute(MyMemberInfo[i], typeof (DeveloperAttribute));
        if (att == null)
        {
            Console.WriteLine("No attribute in member function {0}.\n" , MyMemberInfo[i].ToString());
        }
        else
        {
            Console.WriteLine("The Name Attribute for the {0} member is: {1}.",
                MyMemberInfo[i].ToString(), att.Name);
            Console.WriteLine("The Level Attribute for the {0} member is: {1}.",
                MyMemberInfo[i].ToString(), att.Level);
            Console.WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
                MyMemberInfo[i].ToString(), att.Reviewed);
        }
    }
}

如果在方法級別或類級別找不到 DeveloperAttribute 例項,GetAttribute 方法會通知使用者找不到特性,並顯示不包含該特性的方法名稱或類名稱。 如果找到特性,控制檯會顯示 Name、Level 和 Reviewed 欄位。

可以使用 Type 類的成員,在傳遞的類中獲取各個方法和成員。 此示例先查詢 Type 物件,以獲取類級別的特性資訊。 接下來,它使用 Type.GetMethods 將所有方法例項都放入 System.Reflection.MemberInfo 物件陣列,以檢索方法一級的屬性資訊。 還可以使用 Type.GetProperties 方法檢查屬性一級的屬性,或使用 Type.GetConstructors方法檢查建構函式一級的屬性。