這個東西有的叫定制特性,然而我喜歡直接叫特性,但是這樣的話一些人不知道我說的是什麼,如果我說是Attribute的話那麼知道的或者用過的就都懂了。
還記得講到枚舉和位標志那一章,關於位標志,有一個[Flags]的用法。
[ComVisible(true)] [Flags] public enum FileAttributes { /***/ }
這裡的ComVisible和Flags就是特性。
特性的作用
利用特性可宣告式地為自己的代碼構造添加注解來實現特殊功能。它相當於往元數據表裡寫附加信息,使得能在運行時查詢到這些附加信息,從而動態改變代碼的執行方式。
(實際上特性在編譯後就是被序列化到元數據表中,然後獲取時再反序列化為實例。)
比如下面這些例子:
特性的應用
應用方式都很簡單就是將特性放置在目標元素前的一對方括號中。
應用特性時,C#允許用一個前綴明確指定特性要應用於的目標元素。
[type:SomeAttr]//應用於類型 public class People<[typevar:SomeAttr]T> {//應用於泛型 [field:SomeAttr]//應用於字段 public Int32 age; [return:SomeAttr]//應用於返回值 [method:SomeAttr]//應用於方法 public Int32 Eat( [param:SomeAttr]//應用於參數 String foodName) { return 1; } [property:SomeAttr]//應用於屬性 public String SomeProp { [method:SomeAttr]//應用於訪問器方法 get { return null; } } [event: SomeAttr]//應用於事件 [field: SomeAttr]//應用於編譯器生成的字段 [method:SomeAttr]//應用於編譯器生成的add和remove方法 public event EventHandler DieEvent; }
當然還有更上面的assembly和module分別應用於程序集和模塊。
特性實際上是一個類的實例。特性類必須直接或者間接從公共抽象類System.Attribute派生。就比如[Flags]特性實際上定義它的特性類為FlagsAttribute,只不過C#允許應用特性的時候省略Attribute以簡化代碼。
特性的應用還有一種特殊的語法:
[DllImport("Kernel32",CharSet=CharSet.Auto,SetLastError=true)]
命名DllImportAttribute類只有一個接受一個String參數的構造器,但是上述應用中不僅提供了一個String參數,還多給了兩個參數。實際上上面的語法中"Kernel32"叫定位參數,它是強制性的,而另外兩個參數叫命名參數,它的作用是允許在構造好的特性對象中設置對象的任何公共字段和屬性。
還可以將多個特性放在一個方括號中使用
[SomeAttr,AnotherAttr]
特性的定義
現在讓我們去定一個特性
[AttributeUsage(AttributeTargets.Class,AllowMultiple =false,Inherited =false)]//這個特性用於限定People特性只能用於類上,如果不加這個特性默認是不限制的.後面兩個屬性為不允許為一個元素多次指定People特性,且元素的People特性不被繼承 public class PeopleAttribute:System.Attribute {//這裡類名後面加後綴Attribute是為了符合微軟的標准,當然也可以不加 private string _sex; public PeopleAttribute() { } public string Sex { get { return _sex; } } }
特性應該足夠簡單,因為特性實際上只是一個標識作用,記錄一些類的附加信息,而不是寫一些很復雜的代碼到裡面。
特性的字段和屬性的類型也很簡單,只能用基元類型,Type和枚舉類型。(也可以用一維0基數組,但應該盡量避免。)
檢測特性
通常特性都是和反射一起玩的,因為一般都是用反射去檢測特性的存在,或者去獲取特性的信息。(我記得以前自己寫ORM的時候就是用反射加特性)
typeof(FileAttributes).IsDefined(typeof(FlagsAttribute), false); //用於判斷FileAttributes這個類是否應用了[Flags]特性,答案當然是true
然而這個僅僅是用於檢測特性,實際上我們更多的時候是獲取特性裡的一些屬性的信息,那麼就要獲取特性實例對象。
object[] arr= typeof(FileAttributes).GetCustomAttributes(typeof(FlagsAttribute), false);
上面的兩個例子都是System.Reflection命名空間各個類型類(如:Assembly,MemberInfo,FieldInfo等)定義的方法,裡面每個類都提供了IsDefined和GetCustomAttributes方法。
還有一個是System.Reflection.CustomAttributeExtensions這個靜態類也提供了一批靜態方法去檢測,並且更好用。其中GetCustomAttributes直接返回Attribute[]而不是之前的Object[]。
檢測特性(不創建從Attribute派生的對象)
前面那些檢測方法除了IsDefined外,都會在內部調用特性類的構造器,可能還會調用屬性的Get和set訪問器方法。如果是首次訪問類型還會調用類型構造器。
這些方法或者構造器中,如果有每次查找特性都要執行的代碼,那麼就會存在安全隱患。(所以說如果特性類足夠簡單其實不需要用到這種檢測特性的)
所以有了System.Reflection.CustomAttributeData類,在查找特性時禁止執行特性類中的代碼。
兩個特性實例的相互匹配
System.Attribute重寫了Object的Equals方法,會在內部比較兩個對象的類型。不一致會返回false,一致會利用反射來比較兩個特性對象中的字段值(為每個字段調用Equals)。所有字段匹配就返回true否則false。
可在自己定義的特性類中重寫Equals來移除反射的使用,從而提高性能。(記得重寫Equals時要重寫GetHashCode)
System.Attribute還公開了虛方法Match,它的默認實現只是調用Equals並返回結果,然而我們重寫它可以實現更多的匹配效果。
條件特性類
System.Diagnostics.ConditionalAttribute特性類稱為條件特性類。
#define TroyTest
[Conditional("TroyTest"), Conditional("Verify")] public class PeopleAttribute:System.Attribute { public PeopleAttribute() { } }
然後現在如果People特性應用到了某元素如一個類Man上,那麼編譯後只有當定義了TroyTest或者Verify符號的情況下,才會向Man的元數據中寫入特性信息。(不過People類的定義元數據和實現還在程序集中,畢竟它是一個類,只是不向Man的元數據中寫附加信息而已)
#define Test這個語法要寫在文件最頂部,也就是using上方。
參考#define 用法地點:https://msdn.microsoft.com/zh-cn/library/yt3yck0x.aspx
PS:
這兩天換了套博客皮膚,自己也寫了部分樣式,最6的是拿畫圖工具改了兩張阿狸的圖。
看了一下效果,感覺還是蠻有成就感的。