[C#]Attribute特性(2)——方法的特性及特性参数
?上篇博文[C#]Attribute特性介紹了特性的定義,類的特性,字段的特性,這篇博文將介紹方法的特性及特性參數相關概念。
3.方法的特性?
????? 之所以將這部分單列出來進行討論,是因為對方法的特性查詢的反射代碼不同于對類的特性查詢的反射代碼。在這個例子里,我們將使用一個特性用來定義一種可進行事務處理的方法。???
1 public class TransactionableAttribute : Attribute 2 { 3 public TransactionableAttribute() { } 4 } 1 public class TestClass 2 { 3 [Transactionable] 4 public void Foo() 5 { } 6 public void Bar() 7 { } 8 [Transactionable] 9 public void Baz() 10 { } 11 } 1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Type type = typeof(TestClass); 6 foreach (MethodInfo method in type.GetMethods()) 7 { 8 foreach (Attribute attr in method.GetCustomAttributes()) 9 { 10 if (attr is TransactionableAttribute) 11 { 12 Console.WriteLine("{0} is transactionable.", method.Name); 13 } 14 } 15 } 16 Console.Read(); 17 } 18 }??? 代碼輸出結果為:
????
????? 在這個特殊的例子,僅僅憑借TransactionableAttribute就可以讓代碼知道具有這種特性的方法可以參與事務處理。這也是為什么在這里只有一個簡單的、不帶參數的構造函數,而沒有其他成員。然后TestClass定義三種方法:Foo方法、Bar方法和Baz方法;其中Foo方法和Baz方法方法被定義為具有處理事務能力的方法。請注意您附加一個帶構造函數的特性時,如果這個構造函數不帶參數,您就不需要把左括號和右括號包括進去了。
???? ?現在讓我們看看這個程序當中指的關注的部分,看看怎樣通過方法的特性來查詢類的方法。我們開始先用typeof來獲得TestClass類的System.Type對象。
1 Type type = typeof(TestClass);?????? 然后我們使用Type.GetMethods方法來得到一個MethodInfo對象數組。每一個這樣的對象包括TestClass類的一個方法信息。我們用foreach語句來循環處理我們的每一個方法。
1 foreach (MethodInfo method in type.GetMethods())??? 現在我們有一個MethodInfo對象,我們就可以使用MethodInfo.GetCustomAttributes()方法來得到所有的用戶創建的方法特性。我們還是使用foreach語句來循環處理返回的對象數組。
1 foreach (Attribute attr in method.GetCustomAttributes())???????在代碼的這個地方,我們的方法有了特性。現在,通過使用is操作符,我們來判斷一個特性是不是一個TransactionableAttribute,如果他是,就打印出這個方法的名字。
1 if (attr is TransactionableAttribute) 2 { 3 Console.WriteLine("{0} is transactionable.", method.Name); 4 }特性的參數
????? 在上面的例子中,通過構造函數我們討論了附加特性的使用。現在我們要來看看在前面沒有談到的特性的構造函數的一些方面。
定位參數和命名參數
????? 在上一篇博文字段的特性的例子中,您看到一個名為RefistryKeyAttribute的特性。它的構造函數形式如下:
1 public RegistryKeyAttribute(RegistryHives Hive, string valueName)????? 在這個構造函數聲明之后,通過如下這種形式特性就附加給了一個字段:
1 [RegistryKey(RegistryHives. HKEY_CURRENT_USER,"Foo")] 2 public string Foo;?????? 到此為止,這些都很容易理解。這個構造函數有兩個參數,這兩個參數都是在把一個特性附加給一個字段時用到的。不過,我們可以讓這種編程更簡單。如果這個參數大多數時候都不變,那為什么每次都要讓使用這個類的用戶再費勁地輸入這些參數呢?我們可以使用定位參數(position parameter)和命名參數來給這些參數設置默認值。
???? 定位參數是用在構造函數中的參數。在每次使用特性時它們是必須的參數,并且要必須指明這些參數。在上面的RegistryKeyAttribute例子中,Hive和ValueName都是定位參數。命名參數在特性的構造函數中實際上并沒有定義,更確切地說,它們是非靜態的字段和屬性。因此,在一個特性被實例化時,命名參數讓客戶端能夠設置這個特性的字段和屬性,而不必讓您為客戶端要設置的每一種字段和屬性的可能的組合而創建構造函數。
???? 每一個公共的構造函數都可以定義一系列的定位參數,就像所有類型的類一樣。但是,對于特性來說,一旦它的定位參數被確定,用戶就可以使用FieldOrPropertyName=Value來對某個字段或屬性進行引用。下面我們通過對特性RegistryKeyAttribute的修改來解釋一下這種情況。在這個例子里,我們取RegistryKeyAttribute.ValueName作為一個定位參數,而RegistryKeyAttribute.Hive就成了可選的命名參數。接下來,問題就是“您怎樣才能把一些參數定義為命名參數?”因為只有定位參數——即必需性的——參數才包括在構造函數的定義中,因此我們只需簡單地把這個可選參數從構造函數的定義中刪除即可。然后用戶就可以引用如下部分作為命名參數:非只讀、靜態或常量的任何字段,或包括設置存取器方法或非靜態的setter的任何屬性。因此,為了使RegistryKeyAttribute.Hive成為一個命名參數,我們要把它從構造函數的定義中刪除,因為它作為一個公共的讀/寫屬性已經存在了。
public RegistryKeyAttribute(string valueName)???? 用戶現在就可以用下面的任一種方法來附加特性了:
1 [RegistryKey(“Foo”)] 2 [RegistryKey(“Foo”,Hive=RegistryHives.HKEY_LOCAL_MACHINE)]????? 采用這種方式具有很好的靈活性,您既可以使用字段的默認值,同時,又可以讓用戶能夠在需要的時候用其他值覆蓋原來的默認值。但是要記住:如果用戶沒有設置RegistryKeyAttribute.Hive字段的值,我們怎樣來默認它?您也許會想到,“哦,我們來檢查一下看它是不是在構造函數中設置了。”但是,問題是RegistryKeyAttribute.Hive是一個enum型的,它的底層數據類型是int型——它是一個數值。這就意味著在定義時編譯器已經把它初始化為0了!如果我們測試一下構造函數中RegistryKeyAttribute.Hive的值就會發現它等于0,我們不知道,是由調用程序通過命名參數設置的那個值,還是由編譯器在編譯時,因為它是一個數值型才給它設置了該值。不幸的是,現在能解決這個問題的唯一的途徑是改變這段代碼,讓它的值為0時無效。這可以通過如下改變RegistryHives enum的方式實現:?
1 public enum RegistryHives 2 { 3 HKEY_CLASSES_ROOT=1, 4 HKEY_CURRENT_USER, 5 HKEY_LOCAL_MACHINE, 6 HKEY_USERS, 7 HKEY_CURRENT_CONFIG 8 }????? 現在我們知道,使RegistryKeyAttribute.Hive為0的唯一途徑是編譯器把它初始化為0,并且用戶沒有通過一個命名參數來覆蓋它的初值。我們可以用類似下面的代碼來將其初始化:
1 public RegistryKeyAttribute(string valueName) 2 { 3 this.valueName = valueName; 4 if (this.Hive == 0) 5 { 6 this.hive = RegistryHives.HKEY_CURRENT_USER; 7 } 8 }使用命名參數時的常見錯誤
??????當您使用命名參數時,您必須首先要指定定位參數。之后,由于命名參數是放在字段名或屬性之前的,因此命名參數之間沒有先后順序。下面的例子將導致一個編譯錯誤。
1 //This is an error because postional parameters can't follow 2 //named parameters 3 [RegistryKey(Hive=RegistryHives.HKEY_LOCAL_MACHINE, "Foo")]????? 另外,您不能命名定位參數。編譯器在編譯特性的使用時,它會試著先去解析那些命名參數。然后再試著根據方法特性去解析剩下的——定位參數值。本段下面的代碼無法通過編譯,因為在要解析的參數當中必須半酣至少一個定位參數,如果全部都是命名參數,就會出現提示“No overload for method 'Registrykey Attribute 'takes ' O' arguments”(RegistrykeyAttribute方法沒有參數值,無法進行重載)。
1 [RegistryKey(ValueName="Foo",Hive=RegistryHives.HKEY_LOCAL_MACHINE)]???? 最后,命名參數可以是任何公共的、可存取的字段或屬性——包括setter方法——只要不是靜態的或常量即可。
有效的特性參數類型
????? 特性類的定位參數和命名參數的類型僅限于特性參數類型,這些包括:
- bool,byte,char,double,float,int,long,short,string
- System.Type
- object
- enum類型,前提是它或任何有它嵌套在里面的類型必須是公共的可存取類型——就像在那個使用RegistryHives枚舉的例子中一樣。
- 由上述的任何類型組成的一維數組。
???? 因為有效的參數類型僅局限于上述列出來的類型,因此您不能把一個像類那樣的數據結構傳遞給特性構造函數作為參數。這種限制很有意義,因為特性是在程序設計時附加上的,此時您并沒有這個類(對象)的實例化的實例。使用上面列出來的這些有效類型,您就可以在程序設計時把他們的值固定下來,就是為什么能使用他們的原因。
結語
???? 方法的特性和特性參數就介紹到這里,您如果想了解更多請參考《c#技術內幕》這本書,本文也是摘自這本書,記錄在此,方便回顧,也分享給大家,希望能對您有所幫助。下篇將學習AttributeUsage特性和特性標識符。敬請期待......
?
?
?
| 博客地址: | http://www.cnblogs.com/wolf-sun/ |
| 博客版權: | 本文以學習、研究和分享為主,歡迎轉載,但必須在文章頁面明顯位置給出原文連接。 如果文中有不妥或者錯誤的地方還望高手的你指出,以免誤人子弟。如果覺得本文對你有所幫助不如【推薦】一下!如果你有更好的建議,不如留言一起討論,共同進步! 再次感謝您耐心的讀完本篇文章。 轉載:http://www.cnblogs.com/wolf-sun/p/3392765.html |
總結
以上是生活随笔為你收集整理的[C#]Attribute特性(2)——方法的特性及特性参数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 唐雎不辱使命1,2段原文翻译
- 下一篇: asp.net(c#)将彩色图片变灰阶图