C#中的Attribute屬性(特性)用法

經常在C#的範例程式碼中,看到在類別Class、方法Method上面,帶了一個中括號,裡面寫了一些蠻語意化的字串。這個東西在C#語言裡面用到不少,它源自於System.Attribute,並且在.NET的整體架構中佔了很重要的一環。

Attribute在英文中的意思是指「屬性」,但是這與我們一般在程式語言中理解的屬性是完全不一樣的意思的,更進階一點來說應該翻成「特性」才對。一般來說,在普通的程式碼環境根本不會用到這種Attribute語法,只有在特別的時期,例如Win32 DLL的引用[DllImport],或者是Object Serializable時,才會使用到,Attribute是編譯時期的產物,像剛才講的這兩個時機點,都是要跟編譯器描述(講)一些資訊,以利其編譯之動作。

更簡單的說,Attritube最主要的目的,就是可以提供延伸的資訊給程式碼參考,並可動態去修正後續要進行的動作。

基於上述的理由,我們建立一個CMD環境的程式碼,其中包含了一個自訂的特性類別「Writer」,用來記錄這一段程式是由誰來撰寫,以及相關的參考參數可以取出。另外一個「SomeClass」則是真正的把Attribute的寫法使用上去,你可以發現它甚至可以拿來當成程式碼的註解來使用(這也是這個無用的範例假設的情境),無論是Class或Method都可以支援Attribute,且可以不只一個Attribute。

※註:可支援Attribute的寫法包含了組件Assembly、Module、Type、Property、Event、Field、Method、Param、Return等。

最後我們可以從主控台Main這邊,來進行這兩個類別的操作,程式碼以及相關說明如下:

namespace Attribute
{
  class Program
  {
    static void Main(string[] args)
    {
      //取出操作對象的Type
      System.Type oType = typeof(SomeClass);
      //利用反射取出成員資訊(記住Reflection蠻很耗效能)
      System.Reflection.MemberInfo oItemInfo = oType;
      
      /*取出類別上方的Attribute*/
      //取出類別Class中隱含的屬性(0筆至多筆)
      foreach (Writer oWriter in System.Attribute.GetCustomAttributes(oItemInfo))
      {
        System.Console.Write("Writer id={0}, ", oWriter.id.ToString());
        System.Console.WriteLine("name={0}", oWriter.name);
      }
      
      /*取出方法上方的Attribute*/
      //取出這個類別裡面的所有方法
      foreach (System.Reflection.MethodInfo oMethod in oType.GetMethods())
      {
        //取出類別Method中隱含的屬性(0筆至多筆)
        foreach (var oTemp in System.Attribute.GetCustomAttributes(oMethod))
        {
          //因為有可能取到.ToString()之類的繼承方法,那些並不是我們所需要的,所以必須過濾掉
          if (oTemp.GetType() == typeof(Writer))
          {
            Writer oWriter = (Writer)oTemp;
            System.Console.Write("Method Name: {0}; ", oMethod.Name);
            System.Console.Write("Writer id={0}, ", oWriter.id.ToString());
            System.Console.Write("name={0}, ", oWriter.name);
            System.Console.WriteLine("note={0}", oWriter.note);
          }
        }
      }

      System.Console.Read();
    }
  }

  //無聊的示範類別(單純的示範這個程式用來記載某個類別方法是「誰」寫的)
  [Writer(id=777, name="John")]
  [Writer(id=888, name="Mary")]
  class SomeClass
  {
    [Writer(id=901, name="Programer-1", note="程式設計師一")]
    [Writer(id=902, name="Programer-2", note="程式設計師二")]
    public string ADD()
    { return "Method ADD has been run."; }

    [Writer(id=903, name="Programer-3", note="程式設計師三")]
    public string DEL()
    { return "Method DEL has been run."; }
  }

  //自訂Attribute類別,一定要繼承System.Attribute
  //且可藉由上方的Attribute來進行細部的設定(例如多Attribute的AllowMultiple設定)
  [System.AttributeUsage(
    System.AttributeTargets.All, 
    Inherited=false, 
    AllowMultiple=true)]
  class Writer : System.Attribute
  {
    public int id { get; set; }
    public string name { get; set; }
    public string note { get; set; }
  }
}

由上面的程式碼中,我們可以在執行期時,求得正在運行中的類別的「相關註解」,也可以針對正在運行中的類別中的某個方法進行求取「相關註解」,至於這個「相關註解」到底要幹嘛,就得看你後續的程式的需求了。執行出來的畫面如下:

在Attribute的應用中,如果你用到的是自訂Attribute特性,其實最後大多是準備要用於晚期繫結,不然在執行期要這些無聊的類別註解資訊要幹嘛?晚期繫結在.NET裡面的實作就叫做Reflection,中文稱為反射,這個留在「C#中的Reflection反射(反映)用法」中再來探討。

相關連結:

利用Attribute Type打造一個類MVC的驗證
利用Attribute為ORM型別的屬性添增新色彩

C# Attribute 屬性 特性 中括號 Reflection