C# 的特性 Attribute(學習心得 28)

特性(Attribute):用於,在運行時傳遞程序中各種元素(比如類、方法、結構、枚舉、組件等)的 行為信息 的 聲明性標籤。

您可以通過使用 特性 向程序添加 聲明性信息 。

一個 聲明性標籤 是通過放置在它所應用的元素前面的方括號([ ])來描述的。

特性(Attribute)用於添加 元數據,如編譯器指令和注釋、描述、方法、類等其他信息。

.Net 框架提供了兩種類型的特性:預定義特性和自定義特性。

一、規定特性(Attribute)

語法:

[attribute(positional_parameters, name_parameter = value, ...)]
element

特性(Attribute)的名稱和值是在方括號內規定的。

放置在它所應用的元素之前。

positional_parameters 規定必需的信息。

name_parameter 規定可選的信息。

二、預定義特性(Attribute)

.Net 框架提供了三種預定義特性:

  • AttributeUsage
  • Conditional
  • Obsolete

2.1 AttributeUsage

預定義特性 AttributeUsage 描述了如何使用一個 自定義特性 類。

它規定了特性可應用到的項目的類型。

語法:

[AttributeUsage(
   validon,
   AllowMultiple=allowmultiple,
   Inherited=inherited
)]

參數:

  • 參數 validon 規定特性可被放置的語言元素。它是枚舉器 AttributeTargets 的值的組合。默認值是 AttributeTargets.All
  • 參數 allowmultiple(可選的)為該特性的 AllowMultiple 屬性(property)提供一個布爾值。如果為 true,則該特性是多用的。默認值是 false(單用的)。
  • 參數 inherited(可選的)為該特性的 Inherited 屬性(property)提供一個布爾值。如果為 true,則該特性可被派生類繼承。默認值是 false(不被繼承)。

例:

[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property, 
AllowMultiple = true)]

2.2 Conditional

這個預定義特性標記了一個 條件方法,其執行依賴於指定的 預處理標識符。

它會引起方法調用的條件編譯,取決於指定的值,比如 DebugTrace

例如,當調試代碼時顯示變量的值。

語法:

[Conditional(
   conditionalSymbol
)]

例:

[Conditional("DEBUG")]

例:演示該特性

#define D1 // 這裡就是選擇開關,選擇了運行 D1
using System;
using System.Diagnostics;
namespace helloworld
{
    public class Myclass
    {
        [Conditional("D1")] // 僅在 D1 情況下運行
        public static void M1(string msg)
        {
            Console.WriteLine(msg);
        }

        [Conditional("D2")] // 僅在 D2 情況下運行
        public static void M2(string msg)
        {
            Console.WriteLine(msg);
        }
    
        static void Main()
        {

            Myclass.M1("In Function 1.");
            Myclass.M2("In Function 2.");
            Console.ReadKey();
        }
    }
}

運行結果:

In Function 1.

這裡顯示,只有 D1 條件下的方法被運行了。

如果我們改成 #define D2,則 D2 條件下的被運行。

總結:

使用Conditional是封閉#if和#endif內部方法的替代方法,它更整潔,減少了出錯的機會。

這裡有個注意點,那就是如果設定 [Conditional("DEBUG")] 這裡用的是 「DEBUG」,那麼在 Visual Studio 中會出現奇怪的情況,那就是即使你定義 #define D1,那 「DEBUG」 下的方法依然會運行。

從官網文檔查詢到,那是因為:

在 Visual Studio C#和 Visual Basic 項目中,默認情況下,為調試版本定義 “debug” 條件編譯符號,同時為調試版本和發佈版本定義 “TRACE” 符號。

也就是說,我們在 Visual Basic 運行程序本身就已經定義了 #define DEBUG 這個前提,不管你怎麼設定 #define,這個前提一直在。同時,官方文檔也給出了禁用的方法,需要用的時候可以去查閱。

2.3 Obsolete

這個預定義特性標記了不應被使用的程序實體。

它可以讓您通知編譯器丟棄某個特定的目標元素。

例如,當一個新方法被用在一個類中,但是您仍然想要保持類中的舊方法,您可以通過顯示一個應該使用新方法,而不是舊方法的消息,來把它標記為 obsolete(過時的)。

語法:

[Obsolete(
   message
)]
[Obsolete(
   message,
   iserror
)]

參數:

  • message,是一個字符串,描述項目為什麼過時以及該替代使用什麼。
  • iserror,是一個布爾值。如果該值為 true,編譯器應把該項目的使用當作一個錯誤。默認值是 false(編譯器生成一個警告)。

例:

using System;
public class MyClass
{
   [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
   static void OldMethod()
   {
      Console.WriteLine("It is the old method");
   }
   static void NewMethod()
   {
      Console.WriteLine("It is the new method");
   }
   public static void Main()
   {
      OldMethod();
   }
}

運行結果:

Don't use OldMethod, use NewMethod instead

三、創建自定義特性(Attribute)

.Net 框架允許創建自定義特性,用於存儲聲明性的信息,且可在運行時被檢索。

該信息根據設計標準和應用程序需要,可與任何目標元素相關。

創建並使用自定義特性包含四個步驟:

  • 聲明自定義特性
  • 構建自定義特性
  • 在目標程序元素上應用自定義特性
  • 通過反射訪問特性

最後一個步驟,包含編寫一個簡單的程序,來讀取元數據以便查找各種符號。

元數據是用於描述其他數據的數據和信息。

該程序應使用反射來在運行時訪問特性。

3.1 聲明自定義特性

一個新的自定義特性應派生自 System.Attribute 類。

例:聲明了一個名為 DeBugInfo 的自定義特性

// 一個自定義特性 BugFix 被賦給類及其成員
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute

3.2 構建自定義特性

例:構建一個名為 DeBugInfo 的自定義特性,該特性將存儲調試程序獲得的信息。

存儲下面的信息:

  • bug 的代碼編號
  • 辨認該 bug 的開發人員名字
  • 最後一次審查該代碼的日期
  • 一個存儲了開發人員標記的字符串消息

DeBugInfo 類將帶有三個用於存儲前三個信息的私有屬性(property)和一個用於存儲消息的公有屬性(property)。

所以 bug 編號、開發人員名字和審查日期將是 DeBugInfo 類的必需的定位( positional)參數,消息將是一個可選的命名(named)參數。

每個特性必須至少有一個構造函數。

必需的定位( positional)參數應通過構造函數傳遞。

程序:

// 一個自定義特性 BugFix 被賦給類及其成員
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute
{
  private int bugNo;
  private string developer;
  private string lastReview;
  public string message;

  // 構造函數
  public DeBugInfo(int bg, string dev, string d)
  {
      this.bugNo = bg;
      this.developer = dev;
      this.lastReview = d;
  }

  public int BugNo
  {
      get
      {
          return bugNo;
      }
  }
  public string Developer
  {
      get
      {
          return developer;
      }
  }
  public string LastReview
  {
      get
      {
          return lastReview;
      }
  }
  public string Message
  {
      get
      {
          return message;
      }
      set
      {
          message = value;
      }
  }
}

3.3 應用自定義特性

通過把特性放置在緊接着它的目標之前,來應用該特性

例:

[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
[DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
class Rectangle
{
  // 成員變量
  protected double length;
  protected double width;
  public Rectangle(double l, double w)
  {
      length = l;
      width = w;
  }
  [DeBugInfo(55, "Zara Ali", "19/10/2012",
  Message = "Return type mismatch")]
  public double GetArea()
  {
      return length * width;
  }
  [DeBugInfo(56, "Zara Ali", "19/10/2012")]
  public void Display()
  {
      Console.WriteLine("Length: {0}", length);
      Console.WriteLine("Width: {0}", width);
      Console.WriteLine("Area: {0}", GetArea());
  }
}

我們可以使用反射 Reflection 類對象來檢索這些信息。

例:

using System;
// 1. 創建一個自定義特性
// 描述如何使用一個自定義特性 SomethingAttribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]    

//********自定義特性SomethingAttribute**************//
public class SomethingAttribute : Attribute    
{
    private string name; // 名字
    private string data; // 日期
    public string Name // 屬性
    {
        get { return name; }
        set { name = value; }
    }
    public string Data // 屬性
    {
        get { return data; }
        set { data = value; }
    }
    
  	// 構造函數傳遞必須的定位參數
  	public SomethingAttribute(string name)
    {
        this.name = name;
        this.name = name;
    }
}

// 2. 實例化自定義特性
[Something("Amy", Data = "2018-06-18")]
[Something("Jack", Data = "2018-06-18")]
class Test{}

// 3. 獲取自定義特性的中的變量(該部分待驗證)
Type t = typeof(Test);
var something = t.GetCustomAttributes(typeof(SomethingAttribute),true);
foreach(SomethingAttribute each in something)
{
    Console.WriteLine("Name:{0}", each.Name);
    Console.WriteLine("Data:{0}", each.Data);
}
Tags: