一、什么是特性
特性是一種允許我們向程序的程序集添加元數據的語言結構,它是用於保存程序結構信息的某種特殊類型的類。
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(); } } }