C# 的特性 Attribute(學習心得 28)
特性(Attribute):用於,在運行時傳遞程序中各種元素(比如類、方法、結構、枚舉、組件等)的 行為信息 的 聲明性標籤。
您可以通過使用 特性 向程序添加 聲明性信息 。
一個 聲明性標籤 是通過放置在它所應用的元素前面的方括號([ ])來描述的。
特性(Attribute)用於添加 元數據,如編譯器指令和注釋、描述、方法、類等其他信息。
.Net 框架提供了兩種類型的特性:預定義特性和自定義特性。
二、預定義特性(Attribute)
2.1 AttributeUsage
2.2 Conditional
2.3 Obsolete
三、創建自定義特性(Attribute)
3.1 聲明自定義特性
3.2 構建自定義特性
3.3 應用自定義特性
一、規定特性(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
這個預定義特性標記了一個 條件方法,其執行依賴於指定的 預處理標識符。
它會引起方法調用的條件編譯,取決於指定的值,比如 Debug 或 Trace。
例如,當調試代碼時顯示變量的值。
語法:
[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);
}