C# 特性(attribute)
一、什麼是特性
特性是一種允許我們向程式的程式集新增元資料的語言結構,它是用於儲存程式結構資訊的某種特殊型別的類。
MSDN中對它的解釋是:特性提供功能強大的方法以將宣告資訊與 C# 程式碼(型別、方法、屬性等)相關聯。特性與程式實體關聯後,即可在執行時使用名為“反射”的技術查詢屬性。
(有關元資料和反射的知識,點選檢視C# 反射)
二、使用特性
特性的目的是告訴編譯器把程式結構的某組元資料嵌入程式集,它可以放置在幾乎所有的宣告中(但特定的屬性可能限制在其上有效的宣告型別)。其語法為:
● 在結構前放置特性片段來運用特性
● 特性片段被方括號包圍,其中是特性名和特性的引數列表
例:
[Serializable] //不含引數的特性 public class MyClass {...} [MyAttribute("firt","second","finally")] //帶有引數的特性
public class MyClass {...}
注: 大多數特性只針對直接跟隨在一個或多個特性片段後的結構
單個結構可以運用多個特性,使用時可以把獨立的特性片段互相疊在一起或使用分成單個特性片段,特性之間用逗號分隔
[Serializable] [MyAttribute("firt","second","finally")] //獨立的特性片段 ...
[MyAttribute("firt","second","finally"), Serializable] //逗號分隔
...
某些屬性對於給定實體可以指定多次。例如,Conditional就是一個可多次使用的屬性:
[Conditional("DEBUG"), Conditional("TEST1")] void TraceMethod() { // ... }
特性的目標是應用該特性的實體。例如,特性可以應用於類、特定方法或整個程式集。預設情況下,特性應用於它後面的元素。但是,您也可以顯式標識要將特性應用於方法還是它的引數或返回值。
C#中標準特性目標名稱 | 適用物件 |
assembly | 整個程式集 |
module | 當前程式集模組(不同於Visual Basic 模組) |
field | 在類或結構中的欄位 |
event | Event |
method | 方法或get和set屬性訪問器 |
param | 方法引數或set屬性訪問器的引數 |
property | Property |
return | 方法、屬性索引器或get屬性訪問器的返回值 |
type | 結構、類、介面、列舉或委託 |
typevar | 指定使用泛型結構的型別引數 |
三、自定義特性
特性的用法雖然很特殊,但它只是某個特殊型別的類。
3.1 宣告自定義的特性
總體上宣告特性和宣告其他類是一樣的,只是所有的特性都派生自System.Attribute。根據慣例,特性名使用Pascal命名法並且以Attribute字尾結尾,當為目標應用特性時,我們可以不使用字尾。如:對於SerializableAttribute
和MyAttributeAttribute這兩個特性,我們在把它應用到結構的時候可以使用[Serializable和MyAttribute短名
public class MyAttributeAttribute : System.Attribute {...}
當然它也有建構函式。和其他類一樣,每個特性至少有一個公共建構函式,如果你不宣告建構函式,編譯器會產生一個隱式、公共且無參的建構函式。
public class MyAttributeAttribute : System.Attribute { public string Description; public string ver; public string Reviwer; public MyAttributeAttribute(string desc,string ver,string Rev) //建構函式 { Description = desc; this.ver = ver; Reviwer = Rev; } }
3.2 限制特性的使用
前面我們已經知道,可以在類上面運用特性,而特性本身就是類,有一個很重要的預定義特性AttributeUsage可以運用到自定義特性上,我們可以用它來限制特性使用在某個目標型別上
下面的例子使自定義的特性只能應用到方法和類上
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class MyAttributeAttribute : System.Attribute {...}
簡單解讀一下AttributeUsage特性,它有三個重要的公共屬性,如下表
名字 | 意義 | 預設值 |
ValidOn | 指定特性允許的目標型別。建構函式的第一個引數必須是AttributeTarget型別的列舉值 | |
Inherited | 布林值,指示特效能否被派生類和重寫成員繼承 | true |
AllowMultiple | 布林值,指示特效能否被重複放置在同一個程式實體前多次 | false |
在vs中按f12查閱定義我們可以看到,AttributeTarget列舉的成員有
看一個小例子
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, //必須的,指示MyAttribute只能應用到類和方法上 Inherited = false, //可選,表明不能被派生類繼承 AllowMultiple = false)] //可選,表明不能有MyAttribute的多個例項應用到同一個目標上 public class MyAttributeAttribute : System.Attribute {...}
3.3訪問特性
定義好特性了,怎麼進行訪問呢?對於自定義的特性,我們可以用Type中的IsDefined和GetCustomAttributes方法來獲取
3.3.1 使用IsDefined方法
public abstract bool IsDefined(Type attributeType, bool inherit),它是用來檢測某個特性是否應用到了某個類上
引數說明: attributeType :要搜尋的自定義特性的型別。 搜尋範圍包括派生的型別。
inherit:true 搜尋此成員繼承鏈,以查詢這些屬性;否則為 false。 屬性和事件,則忽略此引數
返回結果:true 如果一個或多個例項 attributeType 或其派生任何的型別為應用於此成員; 否則為 false。
下面程式碼片段是用來檢查MyAttribute特性是否被運用到MyClass類
MyClass mc = new MyClass(); Type t = mc.GetType(); bool def = t.IsDefined(typeof(MyAttributeAttribute),false); if (def) Console.WriteLine("MyAttribute is defined!");
3.3.2 使用GetCustomAttributes方法
public abstract object[] GetCustomAttributes(bool inherit),呼叫它後,會建立每一個與目標相關聯的特性的例項
引數說明: inherit:true 搜尋此成員繼承鏈,以查詢這些屬性;否則為 false
返回結果:返回所有應用於此成員的自定義特性的陣列,因此我們必須將它強制轉換為相應的特性型別
//自定義特性 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class MyAttributeAttribute : System.Attribute { public string Description; public string ver; public string Reviwer; public MyAttributeAttribute(string desc,string ver,string Rev) { Description = desc; this.ver = ver; Reviwer = Rev; } } //定義類 [MyAttribute("firt","second","finally")] class MyClass { } static void Main(string[] args) { MyClass mc = new MyClass(); Type t = mc.GetType(); Object[] obj = t.GetCustomAttributes(false); foreach(Attribute a in obj) { MyAttributeAttribute attr = a as MyAttributeAttribute; if(attr != null) { Console.WriteLine("Description : {0}", attr.Description); Console.WriteLine("ver : {0}", attr.ver); Console.WriteLine("review: {0}", attr.Reviwer); } } }
結果:
四、預定義的特性
4.1 Obsolete特性
Obsolete特性將public ObsoleteAttribute()程式結構標註為過期的,並且在程式碼編譯時顯式有用的警告資訊,它有三種過載
public ObsoleteAttribute()
public ObsoleteAttribute(string message) 引數說明: message:描述了可選的變通方法文字字串。
public ObsoleteAttribute(string message, bool error) 引數說明:message:描述了可選的變通方法文字字串。 error:true 如果使用過時的元素將生成編譯器錯誤; false 如果使用它將生成編譯器警告。
舉個例子:
using System; using System.Runtime.CompilerServices; namespace 特性 { class Program { [Obsolete("Use method SuperPrintOut")] static void Print(string str,[CallerFilePath] string filePath = "") { Console.WriteLine(str); Console.WriteLine("filePath {0}", filePath); } static void Main(string[] args) { string path = "no path"; Print("nothing",path); Console.ReadKey(); } } }
執行沒有問題,不過出現了警告:
如果將[Obsolete("Use method SuperPrintOut")] 改成[Obsolete("Use method SuperPrintOut",true)] 的話,編譯則會出現錯誤資訊
4.2 Conditional 特性
public ConditionalAttribute(string conditionString),指示編譯器,如果定義了conditionString編譯符號,就和普通方法沒有區別,否則忽略程式碼中方法這個方法的所有呼叫
#define fun //定義編譯符號 using System; using System.Runtime.CompilerServices; namespace 特性 { class Program { [Conditional("fun")] static void Fun(string str) { Console.WriteLine(str); } static void Main(string[] args) { Fun("hello"); Console.ReadKey(); } } }
由於定義了fun,所以Fun函式會被呼叫,如果沒有定義,這忽略Fun函式的呼叫
4.3 呼叫者資訊特性
呼叫者資訊特性可以訪問檔案路徑、程式碼行數、呼叫成員的名稱等原始碼資訊,這三個特性的名稱分別為CallerFilePath、CallerLineNumber和CallerMemberName,這些方法只能用於方法中的可選引數
using System; using System.Runtime.CompilerServices; namespace 特性 { class Program { static void Print(string str, [CallerFilePath] string filePath = "", [CallerLineNumber] int num = 0, [CallerMemberName] string name = "") { Console.WriteLine(str); Console.WriteLine("filePath {0}", filePath); Console.WriteLine("Line {0}", num); Console.WriteLine("Call from {0}", name); } static void Main(string[] args) { Print("nothing"); Console.ReadKey(); } } }