特性提供功能強大的方法,用以將元數據或聲明信息與代碼(程序集、類型、方法、屬性等)相關聯。這些元數據是在編譯過程中創建,並嵌入到程序集中。特性與程序實體關聯后,即可在運行時使用名為“反射”的技術查詢特性。反射是個普通術語,它描述了在運行過程中檢查和處理程序元素的功能。
特性具有以下屬性:
-
特性可向程序中添加元數據。元數據是有關在程序中定義的類型的信息。所有的 .NET 程序集都包含指定的一組元數據,這些元數據描述在程序集中定義的類型和類型成員。可以添加自定義特性,以指定所需的任何附加信息。
-
可以將一個或多個特性應用到整個程序集、模塊或較小的程序元素(如類和屬性)。
-
特性可以與方法和屬性相同的方式接受參數。
- 程序可以使用反射檢查自己的元數據或其他程序內的元數據。
元數據是什么?
你注意過程序及編譯的時候的pdb文件了嗎?pdb文件里面存儲了,關於程序集內部的所有的成員信息,例如,成員的數據類型,屬性類型,方法返回值,方法入參類型,就是程序及內部所有的定義信息的數據信息,是存儲定義信息的一類數據信息,程序集里面的所有的關於聲明類的數據信息,包括方法間調用,都是存儲在元數據里面。
詳細查看:http://www.cnblogs.com/DswCnblog/p/5344119.html
特性可以放置在幾乎所有的聲明中。在 C# 中,特性的指定方法為:將括在方括號中的特性名置於其應用到的實體的聲明上方,一個元素上面可以放置多個特性
[System.Serializable] public class SampleClass { // Objects of this type can be serialized. }
根據約定,所有特性名稱都以單詞“Attribute”結束,以便將它們與“.NET Framework”中的其他項區分。在代碼中使用特性,C#編譯器會把字符串Attribute追加到這個特性名稱后面,然后在其搜索路徑的所有名稱空間中搜索指定名稱的類。如果該特性的名稱已字符串Attribute結尾,編譯器就不會把字符串加到組合名稱中。
預定義特性(Attribute)
.Net 框架提供了三種預定義特性:
- AttributeUsage
- Conditional
- Obsolete
AttributeUsage特性
預定義特性 AttributeUsage 主要用於標示自定義特性可以應用到哪些類型的程序元素上,這個信息由第一個參數給出。
規定該特性的語法如下:
[AttributeUsage( validon, //規定特性可被放置的語言元素,它是枚舉器 AttributeTargets 的值的組合,默認值是 AttributeTargets.All AllowMultiple=allowmultiple, //如果為 true,則該特性可以在同一個元素多次使用,默認值是 false(不可多次使用) Inherited=inherited //如果為 true,則該特性可被派生類繼承,默認值是 false(不被繼承) )]
例如:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
AttributeTargets :
成員名稱 | 說明 | |
---|---|---|
Assembly | 可以對程序集應用特性。 | |
Module | 可以對模塊應用特性。
![]()
Module 指的是可移植的可執行文件(.dll 或 .exe),而非 Visual Basic 標准模塊。
|
|
Class | 可以對類應用特性。 | |
Struct | 可以對結構應用特性,即值類型。 | |
Enum | 可以對枚舉應用特性。 | |
Constructor | 可以對構造函數應用特性。 | |
Method | 可以對方法應用特性。 | |
Property | 可以對屬性應用特性。 | |
Field | 可以對字段應用特性。 | |
Event | 可以對事件應用特性。 | |
Interface | 可以對接口應用特性。 | |
Parameter | 可以對參數應用特性。 | |
Delegate | 可以對委托應用特性。 | |
ReturnValue | 可以對返回值應用特性。 | |
GenericParameter | 可以對泛型參數應用特性。
![]()
目前,此特性可以應用僅於 C#、Microsoft 中間語言 (MSIL) 和發出的代碼。
|
|
All | 可以對任何應用程序元素應用特性。 |
按照上面的經驗,再次開始動手來熟悉這一切,我指定了該自定義的特性不可繼承,就在不解釋別的了只是為了證明一下命名參數Inherited定性成功與否,總之還是很簡單的。
namespace { class Program { static void Main(string[] args) { GetAttributeInfo(typeof(OldClass)); Console.WriteLine("=============="); GetAttributeInfo(typeof(NewClass)); Console.ReadKey(); } public static void GetAttributeInfo(Type t) { OldAttribute myattribute = (OldAttribute)Attribute.GetCustomAttribute(t, typeof(OldAttribute)); if (myattribute == null) { Console.WriteLine(t.ToString()+"類中自定義特性不存在!"); } else { Console.WriteLine("特性描述:{0}\n加入事件{1}", myattribute.Discretion, myattribute.date); } } } [AttributeUsage(AttributeTargets.Class,Inherited=false)] //設置了定位參數和命名參數 class OldAttribute : Attribute //繼承自Attribute { private string discretion; public string Discretion { get { return discretion; } set { discretion = value; } }
public DateTime date; public OldAttribute(string discretion) { this.discretion = discretion; date = DateTime.Now; } }
//現在我們定義兩類 [Old("這個類將過期")]//使用定義的新特性 class OldClass { public void OldTest() { Console.WriteLine("測試特性"); } }
class NewClass:OldClass { public void NewTest() { Console.WriteLine("測試特性的繼承"); } } //我們寫一個方法用來獲取特性信息 }
運行效果:
Conditional
這個預定義特性標記了一個條件方法,其執行依賴於它頂的預處理標識符。
它會引起方法調用的條件編譯,取決於指定的值,比如 Debug 或 Trace。例如,當調試代碼時顯示變量的值。
#define DEBUG //定義DEBUG宏,則下面有輸出,不定義則沒有輸出 using System; using System.Diagnostics; public class Myclass { [Conditional("DEBUG")] public static void Message(string msg) { Console.WriteLine(msg); } }
class Test { static void function1() { Myclass.Message("In Function 1."); function2(); } static void function2() { Myclass.Message("In Function 2."); } public static void Main() { Myclass.Message("In Main function."); function1(); Console.ReadKey(); } }
ObsoleteAttribute
這個預定義特性標記了不應被使用的程序實體。它可以讓您通知編譯器丟棄某個特定的目標元素。例如,當一個新方法被用在一個類中,但是您仍然想要保持類中的舊方法,您可以通過顯示一個應該使用新方法,而不是舊方法的消息,來把它標記為 obsolete(過時的)。
public ObsoleteAttribute(string message, bool error)
參數 類型:
message System ..::.String 描述可選的變通方法的文本字符串。
error System ..::.Boolean 指示是否將使用已過時的元素視為錯誤的布爾值。
using System; public class MyClass { [Obsolete("Don't use OldMethod, use NewMethod instead", true)] static void OldMethod() { Console.WriteLine("It is the old method"); } public static void Main() { OldMethod(); //將會報錯 } }
自定義特性
通過定義一個特性類,該特性類直接或間接地從Attribute派生,有助於方便快捷地在元數據中標識特性定義。特性類本身用一個特性-System.AttributeUsage特性來標記,這個是Microsoft定義的特性,AttributeUsage主要用於表示自定義特性可以應用到哪些類型的程序上。這些信息由它的第一個參數給出,該參數是必選的,其類型是枚舉類型AttributeTarget。
一個新的自定義特性應派生自 System.Attribute 類,例如:
// 一個自定義特性 BugFix 被賦給類及其成員 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute //已經聲明了一個名為 DeBugInfo 的自定義特性。
讓我們構建一個名為 DeBugInfo 的自定義特性,該特性將存儲調試程序獲得的信息。它存儲下面的信息:
- bug 的代碼編號
- 辨認該 bug 的開發人員名字
- 最后一次審查該代碼的日期
- 一個存儲了開發人員標記的字符串消息
我們的 DeBugInfo 類將帶有三個用於存儲前三個信息的私有屬性(property)和一個用於存儲消息的公有屬性(property)。所以 bug 編號、開發人員名字和審查日期將是 DeBugInfo 類的必需的定位( positional)參數,消息將是一個可選的命名(named)參數。
每個特性必須至少有一個構造函數。必需的定位( positional)參數應通過構造函數傳遞。下面的代碼演示了 DeBugInfo 類:
// 一個自定義特性 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; } } }
應用自定義特性
通過把特性放置在緊接着它的目標之前,來應用該特性:
[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()); } }