跳轉至內容

.NET 開發基金會/屬性

來自 Wikibooks,開放世界中的開放書籍


反射:構建自定義屬性


附錄:構建自定義屬性

[編輯 | 編輯原始碼]

此頁面原始文字由 William "Scott" Baker 撰寫

屬性摘要

[編輯 | 編輯原始碼]

屬性是一種“標記”程式碼元資料元素的方法,使用描述性資訊,這些資訊可以在執行時使用反射訪問。屬性必須直接或間接派生自System.Attribute。.NET 框架中存在大量屬性;您也可以定義自己的屬性。在程式碼中使用屬性有三個方面

  1. 定義自定義屬性類,包括以下步驟
    1. AttributeUsageAttribute屬性分配給您的類。
    2. 編寫程式碼來定義您的自定義屬性類。
    3. 為您的類建立引數。
  2. 將屬性分配給程式碼成員。
  3. 在執行時檢索屬性資訊。

建立自定義屬性類

[編輯 | 編輯原始碼]

如前所述,.NET 框架中存在多個預定義屬性;您可能已經在程式碼中使用過它們。特別是 XML 解析器在(反)序列化物件時嚴重依賴屬性。您也可以定義自己的自定義屬性,如下所示。定義自定義屬性包括三個步驟

  1. AttributeUsageAttribute屬性分配給您的類。
  2. 編寫程式碼來定義您的自定義屬性類。
  3. 為您的類建立引數。

"AttributeUsageAttribute"分配給您的類

[編輯 | 編輯原始碼]
屬性的“範圍”和其他特性應透過使用AttributeUsageAttribute屬性來指定。
注意:在 Visual Basic 中,必須在所有自定義屬性上使用AttributeUsageAttribute屬性。將AttributeUsageAttribute屬性應用於類
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute 
{
  // ...
}

請注意使用“AttributeUsage”與“AttributeUsageAttribute”。按照慣例,所有屬性都以“Attribute”字尾命名 - 但在程式碼中使用時可以省略字尾。這對於使用者定義的屬性也是如此;QualityCheckAttribute屬性可以引用為

[QualityCheck] // or... 
[QualityCheckAttribute]

AttributeUsageAttribute具有三個成員ValidOn、AllowMultipleInherited.

  • ValidOn成員接受AttributeTargets列舉值,並將您的屬性限制為您指定的程式碼型別。預設值為AttributeTargets.All。您可以將您的屬性限制為類、列舉、返回值或以下列表中的任何內容
All (any element)   Delegate    GenericParameter    Parameter
Assembly            Enum        Interface           Property
Class               Event       Method              ReturnValue
Constructor         Field       Module*             Struct

*Module refers to a portable executable (.exe or .dll), and not a Visual Basic standard module.

您還可以將目標型別組合為按位 OR 運算以指定多個可接受值

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  • AllowMultiple是一個布林值,它確定是否可以將屬性多次應用於給定成員。預設值為false。以下示例說明了在程式碼元素上使用多個相同屬性的例項
[QualityCheck("Scott Baker", "02/28/06", IsApproved = true,
   Comment = "This code follows all established guidelines.  Release approved.")]
[QualityCheck("Matt Kauffman", "01/15/06", IsApproved = false,
   Comment = "Code quality much improved. Minor revision required.")]
[QualityCheck("Joe Schmoe", 01/01/06", IsApproved = false,
   Comment = "This code is a mess and needs a complete rewrite")]
public class MyClass
{
// ... 
}
  • Inherited成員確定是否將類上設定的屬性繼承到繼承樹中的更低級別類。預設值為 true
[AttributeUsage(AttributeTargets.Class)]
public class AttrOneAttribute : Attribute
{
  // ... 
}

// This attribute will not be inherited 
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AttrTwoAttribute : Attribute
{
  // ... 
}

[AttrOne]
[AttrTwo]
public class ClassOne 
{
  // ... 
}

// This class inherits AttrOne from ClassOne, 
// but not AttrTwo 
public class ClassTwo : ClassOne
{
  // ... 
}

定義自定義屬性類

[編輯 | 編輯原始碼]
  • 屬性是繼承自System.Attribute的類,直接或間接繼承
public Class QualityCheckAttribute : System.Attribute // direct 
{
  // ...
}

public Class FinalCheck : QualityCheckAttribute  // indirect 
{
// ...
}
  • 屬性類具有AttributeUsageAttribute屬性
[AttributeUsage(AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute
{
  // ...
}

如前所述,在 VB 中必須使用AttributeUsageAttribute。在 C# 中,如果未宣告,則會自動應用預設值。

為屬性建立引數

[編輯 | 編輯原始碼]
屬性接受兩種型別的引數:位置引數和命名引數。位置引數由類中的公共建構函式定義,必須按定義的順序排列,並且是必需的;命名引數由公共屬性定義,可以按任何順序排列,並且是可選的。
位置引數
[編輯 | 編輯原始碼]
屬性的位置引數由類中的建構函式定義。與任何類一樣,建構函式可以過載,並且可以定義不接受任何引數的預設建構函式。與任何其他類一樣,位置引數的簽名必須與屬性類中建構函式的簽名匹配
public class QualityCheckAttribute : Attribute
{
  public QualityCheckAttribute(string Name, string Date)
  // ... 
}
命名引數
[編輯 | 編輯原始碼]
屬性的命名引數由類中定義的公共屬性定義。命名引數是可選的,並在任何位置引數之後宣告。該IsApproved屬性演示了命名引數
public class QualityCheckAttribute : Attribute
{
  private string _name;
  private string _date;
  private bool isApproved;
  public bool IsApproved
  {
    get {return isApproved;}
    set {isApproved = value;}
  }
  public QualityCheckAttribute(string Name, string Date)
  {
    // ...
  }
}

請記住,程式碼中的變數可以既是位置引數又是命名引數。如果我們要為 _name_date 欄位新增公共屬性,我們可以將它們用作命名引數或位置引數。當然,這並不推薦:必需引數應為位置引數,可選引數應為命名引數。

將屬性分配給程式碼成員

[編輯 | 編輯原始碼]

您已經看到了將屬性分配給程式碼成員的示例。但是,需要澄清一些要點。

  • 消歧義是指在程式碼成員上使用屬性的澄清。
  • 語法 - 應用多個屬性的方法不止一種。

消歧義

[編輯 | 編輯原始碼]
以下程式碼不清楚SomeAttribute是否適用於方法MyMethod或其返回值
public class MyAttribute : Attribute
{
  [SomeAttribute("Hello")]
  public string MyMethod(aString)
  {
    return aString;
  }
}

消除歧義可以解決這些問題。透過指定屬性應用到的程式碼型別,我們可以消除混淆。以下程式碼顯示屬性應用於返回值

public class MyAttribute : Attribute
{
  [return : SomeAttribute]
  public string MyMethod(aString)
  {
    return aString;
  }
}

下表列出了所有允許使用屬性的宣告;對於每個宣告,第二列列出了宣告中屬性的可能目標。**粗體**的目標是預設目標。

Declaration               Possible targets
assembly                  assembly
module                    module
class                     type
struct                    type
interface                 type
enum                      type
delegate                  type, return
method                    method, return
parameter                 param
field                     field
property — indexer        property
property — get accessor   method, return
property — set accessor   method, param, return
event — field             event, field, method
event — property          event, property
event — add               method, param
event — remove            method, param

*Reference: Disambiguating Attribute Targets (C# Programming Guide)

人們可能會認為AttributeUsageAttribute 的 AttributeTargets在屬性定義中將有助於防止這種混淆:這是錯誤的。編譯器在解決衝突時不會使用AttributeUsageAttribute資訊。即使您定義了一個屬性使其僅應用於特定型別,例如AttributeTargets.Return,您仍然必須明確它應用於返回值型別,在應用屬性時,否則編譯器將使用預設目標方法型別,並丟擲錯誤。

語法:應用多個屬性

[編輯 | 編輯原始碼]
將多個屬性應用於成員時,有兩種方法可以做到這一點
[AttrOne(...), AttrTwo(...)]  
  // or...
[AttrOne(...)]
[AttrTwo(...)]

這兩種方法是等效的。請記住,如果您要在單個大括號中指定多個屬性,則它們必須應用於相同的目標型別。否則,您必須為每種型別提供單獨的宣告

[return : AttrOne(...), method : AttrTwo(...)]  // <-- invalid!
  // instead, you must...
[return : AttrOne(...)]
[method : AttrTwo(...)]

在執行時檢索屬性資訊

[編輯 | 編輯原始碼]

能夠宣告和應用屬性,除非我們能夠檢索這些資料並使用它們,否則並不是很有幫助。幸運的是,這是一個簡單的過程。將解決三個基本場景

  1. 從成員中檢索單個屬性。
  2. 從成員中檢索多個屬性。
  3. 從多個成員中檢索單個型別的屬性。

從成員中檢索單個屬性

[編輯 | 編輯原始碼]

要訪問屬性資訊

  1. 宣告屬性型別的例項。
  2. 使用Attribute.GetCustomAttribute(型別, typeof)方法將屬性讀入例項。
  3. 使用例項的屬性來讀取值。

以下示例程式碼聲明瞭一個類ExampleClass具有一個QualityCheck屬性。該GetSingleAttribute方法接受目標成員型別和要查詢的屬性型別。該Attribute.GetCustomAttribute方法將屬性資訊檢索到attr物件中,從中我們可以讀取最重要的IsApproved屬性

[QualityCheck("Scott Baker", "02/04/2006", IsApproved = false)]
public class ExampleClass
{

  public static void Main()
  {
    GetSingleAttribute(typeof(ExampleClass), typeof(QualityCheck))
  }
 
  public static void GetSingleAttribute(Type targetType, Type attrType)
  {
    typeof(attrType) attr = (attrType)Attribute.GetCustomAttribute(targetType, typeof(attrType));
  
    if (attr == null)
    { //... }
    else
    {
      Console.Writeline(attr.IsApproved);
    }
  }

要記住的一個重要因素是GetCustomAttribute方法設計為僅讀取**一個**屬性。GetCustomAttribute實際上檢查是否有多個屬性匹配 - 如果沒有匹配,它將返回null,但如果有多個匹配,它將丟擲AmbiguousMatchException。在檢查屬性時,唯一可以安全地使用GetCustomAttribute的是當屬性的定義宣告[AttributeUsage(AllowMultiple=false)].

從成員中檢索多個屬性

[編輯 | 編輯原始碼]

讀取成員上的多個屬性例項與讀取一個屬性例項並沒有太大區別;要讀取多個屬性,請使用複數GetCustomAttributes,它返回一個屬性陣列。然後,您可以遍歷生成的陣列並讀取值

QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(t, typeof(QualityCheck));

foreach (QualityCheck attr in attrArray)
{
  Console.Writeline(attr.IsApproved);
}

從多個成員中檢索單個屬性型別

[編輯 | 編輯原始碼]

如果要執行更復雜的操作,例如檢查類中的每個方法是否具有 QualityCheck 屬性,該怎麼辦?由於System.Reflection名稱空間,我們甚至不需要費力。只需將所有成員(在本例中為方法)讀入MemberInfo陣列並遍歷它們

using System.Reflection

public class ExampleClass
{
  public static void Main()
  {
    RetrieveAttributes(typeof(ExampleClass));
  }

  public void RetrieveAttributes(Type t)
  {
    MemberInfo[] methodList = t.GetMethods();
    foreach (MemberInfo m in methodList)
    {
      QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(m, typeof(QualityCheck));
      foreach (QualityCheck attr in attrArray)
      {
        Console.Writeline(attr.IsApproved);
      }
    }
  }
}
[編輯 | 編輯原始碼]
華夏公益教科書