.Net 中的反射(反射特性) - Part.3反射特性(Attribute)可能很多人還不了解特性,所以我們先了解一下什么是特性。想想看如果有一個(gè)消息系統,它存在這樣一個(gè)方法,用來(lái)將一則短消息發(fā)送給某人: // title: 標題;author:作者;content:內容;receiverId:接受者Id 我們很快就發(fā)現這樣將參數一個(gè)個(gè)羅列到方法的參數列表中擴展性很糟糕,我們最好定義一個(gè)Message類(lèi)將短消息封裝起來(lái),然后給方法傳遞一個(gè)Message對象: public class Message{ 此時(shí),我們或許應該將舊的方法刪除,用這個(gè)擴展性更好的SendMsg方法來(lái)取代。遺憾的是我們往往不能,因為這組程序可能作為一組API發(fā)布,在 很多客戶(hù)程序中已經(jīng)在使用舊版本的SendMsg()方法,如果我們在更新程序的時(shí)候簡(jiǎn)單地刪除掉舊的SendMsg()方法,那么將造成使用老版本 SendMsg()方法的客戶(hù)程序不能工作。 這個(gè)時(shí)候,我們該如果做呢?我們當然可以通過(guò)方法重載來(lái)完成,這樣就不用刪除舊的SendMsg()方法了。但是如果新的SendMsg()不僅優(yōu) 化了參數的傳遞,并且在算法和效率上也進(jìn)行了全面的優(yōu)化,那么我們將會(huì )迫切希望告知客戶(hù)程序現在有一個(gè)全新的高性能SendMsg()方法可供使用,但此 時(shí)客戶(hù)程序并不知道已經(jīng)存在一個(gè)新的SendMsg方法,我們又該如何做呢?我們可以打電話(huà)告訴維護客戶(hù)程序的程序員,或者發(fā)電子郵件給他,但這樣顯然不 夠方便,最好有一種辦法能讓他一編譯項目,只要存在對舊版本SendMsg()方法的調用,就會(huì )被編譯器告知。 1..Net內置特性介紹.Net 中可以使用特性來(lái)完成這一工作。特性是一個(gè)對象,它可以加載到程序集及程序集的對象中,這些對象包括 程序集本身、模塊、類(lèi)、接口、結構、構造函數、方法、方法參數等,加載了特性的對象稱(chēng)作特性的目標。特性是為程序添加元數據(描述數據的數據)的一種機 制,通過(guò)它可以給編譯器提供指示或者提供對數據的說(shuō)明。 NOTE:特性的英文名稱(chēng)叫做Attribute,在有的書(shū)中,將它翻譯為“屬 性”;另一些書(shū)中,將它翻譯為“特性”;由于通常我們將含有g(shù)et和/或set訪(fǎng)問(wèn)器的類(lèi)成員稱(chēng)為“屬性”(英文Property),所以本文中我將使用 “特性”這個(gè)名詞,以區分“屬性”(Property)。 1.1 System.ObsoleteAttribute 特性我們通過(guò)這個(gè)例子來(lái)看一下特性是如何解決上面的問(wèn)題:我們可以給舊的SendMsg()方法上面加上Obsolete特性來(lái)告訴編譯器這個(gè)方法已經(jīng)過(guò)時(shí),然后當編譯器發(fā)現當程序中有地方在使用這個(gè)用Obsolete標記過(guò)的方法時(shí),就會(huì )給出一個(gè)警告信息。 namespace Attribute { 現在運行這段代碼,我們會(huì )發(fā)現編譯器給出了一個(gè)警告:警告CS0618: “Attribute.TestClass.ShowMsg()”已過(guò)時(shí):“請使用新的SendMsg(Message msg)重載方法”。通過(guò)使用特性,我們可以看到編譯器給出了警告信息,告訴客戶(hù)程序存在一個(gè)新的方法可供使用,這樣,程序員在看到這個(gè)警告信息后,便會(huì )考慮使用新的SendMsg()方法。 NOTE:簡(jiǎn)單起見(jiàn),TestClass類(lèi)和 Program位于同一個(gè)程序集中,實(shí)際上它們可以離得很遠。 1.2 特性的使用方法通過(guò)上面的例子,我們已經(jīng)大致看到特性的使用方法:首先是有一對方括號“[]”,在左方括號“[”后緊跟特性的名稱(chēng),比如Obsolete,隨后是 一個(gè)圓括號“()”。和普通的類(lèi)不同,這個(gè)圓括號不光可以寫(xiě)入構造函數的參數,還可以給類(lèi)的屬性賦值,在Obsolete的例子中,僅傳遞了構造函數參 數。 NOTE:實(shí)際上,當你用鼠標框選住Obsolete,然后按下F12轉到定義,會(huì ) 發(fā)現它的全名是ObsoleteAttribute,繼承自Attribute類(lèi)。但是這里卻僅用Obsolete來(lái)標記方法,這是.Net的一個(gè)約定, 所有的特性應該均以Attribute來(lái)結尾,在為對象標記特性時(shí)如果沒(méi)有添加Attribute,編譯器會(huì )自動(dòng)尋找帶有Attribute的版本。 NOTE:使用構造函數參數,參數的順序必須同構造函數聲明時(shí)的順序相同,所有在特性中也叫位置參數(Positional Parameters),與此相應,屬性參數也叫做命名參數(Named Parameters)。在下面會(huì )詳細說(shuō)明。 2.自定義特性(Custom Attributes)2.1 范例介紹如果不能自己定義一個(gè)特性并使用它,我想你怎么也不能很好的理解特性,我們現在就自己構建一個(gè)特性。假設我們有這樣一個(gè)很常見(jiàn)的需求:我們在創(chuàng )建或 者更新一個(gè)類(lèi)文件時(shí),需要說(shuō)明這個(gè)類(lèi)是什么時(shí)候、由誰(shuí)創(chuàng )建的,在以后的更新中還要說(shuō)明在什么時(shí)候由誰(shuí)更新的,可以記錄也可以不記錄更新的內容,以往你會(huì )怎 么做呢?是不是像這樣在類(lèi)的上面給類(lèi)添加注釋?zhuān)?/p> //更新:Matthew, 這樣的的確確是可以記錄下來(lái),但是如果有一天我們想將這些記錄保存到數據庫中作以備份呢?你是不是要一個(gè)一個(gè)地去查看源文件,找出這些注釋?zhuān)僖粭l條插入數據庫中呢? 通過(guò)上面特性的定義,我們知道特性可以用于給類(lèi)型添加元數據,這些元數據可以用于描述類(lèi)型。那么在此處,特性應該會(huì )派上用場(chǎng)。那么在本例中,元數據應該是:注釋類(lèi)型(“更新”或者“創(chuàng )建”),修改人,日期,備注信息(可有可無(wú))。而特性的目標類(lèi)型是DemoClass類(lèi)。 按照對于附加到DemoClass類(lèi)上的元數據的理解,我們先創(chuàng )建一個(gè)封裝了元數據的類(lèi)RecordAttribute: public class RecordAttribute { NOTE:注意構造函數的參數 date,必須為一個(gè)常量、Type類(lèi)型、或者是常量數組,所以不能直接傳遞DateTime類(lèi)型。 這個(gè)類(lèi)不光看上去,實(shí)際上也和普通的類(lèi)沒(méi)有任何區別,顯然不能它因為名字后面跟了個(gè)Attribute就搖身一變成了特性。那么怎樣才能讓它稱(chēng)為特性并應用到一個(gè)類(lèi)上面呢?進(jìn)行下一步之前,我們看看.Net內置的特性Obsolete是如何定義的: namespace System { 2.2 添加特性的格式(位置參數和命名參數)首先,我們應該發(fā)現,它繼承自Attribute類(lèi),這說(shuō)明我們的RecordAttribute也應該繼承自Attribute類(lèi)。 其次,我們發(fā)現在這個(gè)特性的定義上,又用了三個(gè)特性去描述它。這三個(gè)特性分別是:Serializable、AttributeUsage 和 ComVisible。Serializable特性我們前面已經(jīng)講述過(guò),ComVisible簡(jiǎn)單來(lái)說(shuō)是“控制程序集中個(gè)別托管類(lèi)型、成員或所有類(lèi)型對 COM 的可訪(fǎng)問(wèn)性”(微軟給的定義)。這里我們應該注意到:特性本身就是用來(lái)描述數據的元數據,而這三個(gè)特性又用來(lái)描述特性,所以它們可以認為是“元數據的元數據”(元元數據:meta-metadata)。 因為我們需要使用“元元數據”去描述我們定義的特性 RecordAttribute,所以現在我們需要首先了解一下“元元數據”。這里應該記得“元元數據”也是一個(gè)特性,大多數情況下,我們只需要掌握 AttributeUsage就可以了,所以現在就研究一下它。我們首先看上面AttributeUsage是如何加載到 ObsoleteAttribute特性上面的。 [AttributeUsage(6140, Inherited = false)] 然后我們看一下AttributeUsage的定義: namespace System { 可以看到,它有一個(gè)構造函數,這個(gè)構造函數含有一個(gè)AttributeTargets類(lèi)型的位置參數(Positional Parameter),還有兩個(gè)命名參數(Named Parameter)。注意ValidOn屬性不是一個(gè)命名參數,因為它不包含set訪(fǎng)問(wèn)器。 這里大家一定疑惑為什么會(huì )這樣劃分參數,這和特性的使用是相關(guān)的。假如AttributeUsageAttribute 是一個(gè)普通的類(lèi),我們一定是這樣使用的: // 實(shí)例化一個(gè) AttributeUsageAttribute 類(lèi) 但是,特性只寫(xiě)成一行代碼,然后緊靠其所應用的類(lèi)型(目標類(lèi)型),那么怎么辦呢?微軟的軟件工程師們就想到了這樣的辦法:不管是構造函數的參數 還是 屬性,統統寫(xiě)到構造函數的圓括號中,對于構造函數的參數,必須按照構造函數參數的順序和類(lèi)型;對于屬性,采用“屬性=值”這樣的格式,它們之間用逗號分 隔。于是上面的代碼就減縮成了這樣: [AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)] 可以看出,AttributeTargets.Class是構造函數參數(位置參數),而AllowMutiple 和 Inherited實(shí)際上是屬性(命名參數)。命名參數是可選的。將來(lái)我們的RecordAttribute的使用方式于此相同。(為什么管他們叫參數, 我猜想是因為它們的使用方式看上去更像是方法的參數吧。) 假設現在我們的RecordAttribute已經(jīng)OK了,則它的使用應該是這樣的: [RecordAttribute("創(chuàng )建","張子陽(yáng)"," 其中recordType, author 和 date 是位置參數,Memo是命名參數。 2.3 AttributeTargets 位標記從AttributeUsage特性的名稱(chēng)上就可以看出它用于描述特性的使用方式。具體來(lái)說(shuō),首先應該是其所標記的特性可以應用于哪些類(lèi)型或者對 象。從上面的代碼,我們看到AttributeUsage特性的構造函數接受一個(gè) AttributeTargets 類(lèi)型的參數,那么我們現在就來(lái)了解一下AttributeTargets。 AttributeTargets 是一個(gè)位標記,它定義了特性可以應用的類(lèi)型和對象。 [Flags] 現在應該不難理解為什么上面我范例中用的是: [AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)] 而ObsoleteAttribute特性上加載的 AttributeUsage是這樣的: [AttributeUsage(6140, Inherited = false)] 因為AttributeUsage是一個(gè)位標記,所以可以使用按位或“|”來(lái)進(jìn)行組合。所以,當我們這樣寫(xiě)時(shí): [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface) 意味著(zhù)既可以將特性應用到類(lèi)上,也可以應用到接口上。 NOTE:這里存在著(zhù)兩個(gè)特例:觀(guān)察上面AttributeUsage的定義,說(shuō)明 特性還可以加載到程序集Assembly和模塊Module上,而這兩個(gè)屬于我們的編譯結果,在程序中并不存在這樣的類(lèi)型,我們該如何加載呢?可以使用這 樣的語(yǔ)法:[assembly:SomeAttribute(parameter list)],另外這條語(yǔ)句必須位于程序語(yǔ)句開(kāi)始之前。 2.4 Inherited 和 AllowMutiple屬性AllowMutiple 屬性用于設置該特性是不是可以重復地添加到一個(gè)類(lèi)型上(默認為false),就好像這樣: [RecordAttribute("更新","Jimmy"," 所以,我們必須顯示的將AllowMutiple設置為T(mén)rue。 Inherited 就更復雜一些了,假如有一個(gè)類(lèi)繼承自我們的DemoClass,那么當我們將RecordAttribute添加到DemoClass上 時(shí),DemoClass的子類(lèi)也會(huì )獲得該特性。而當特性應用于一個(gè)方法,如果繼承自該類(lèi)的子類(lèi)將這個(gè)方法覆蓋,那么Inherited則用于說(shuō)明是否子類(lèi) 方法是否繼承這個(gè)特性。 在我們的例子中,將 Inherited 設為false。 2.5 實(shí)現 RecordAttribute現在實(shí)現RecordAttribute應該是非常容易了,對于類(lèi)的主體不需要做任何的修改,我們只需要讓它繼承自Attribute基類(lèi),同時(shí)使用AttributeUsage特性標記一下它就可以了(假定我們希望可以對類(lèi)和方法應用此特性): [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=true, Inherited=false)] 2.6 使用 RecordAttribute我們已經(jīng)創(chuàng )建好了自己的自定義特性,現在是時(shí)候使用它了。 [Record("更新", "Matthew", " 這段程序簡(jiǎn)單地在屏幕上輸出一個(gè)“This is a demo class”。我們的屬性也好像使用“//”來(lái)注釋一樣對程序沒(méi)有任何影響,實(shí)際上,我們添加的數據已經(jīng)作為元數據添加到了程序集中??梢酝ㄟ^(guò)IL DASM看到: ![]() 3.使用反射查看自定義特性利用反射來(lái)查看 自定義特性信息 與 查看其他信息 類(lèi)似,首先基于類(lèi)型(本例中是DemoClass)獲取一個(gè)Type對象,然后調用Type對象的GetCustomAttributes()方法,獲取 應用于該類(lèi)型上的特性。當指定GetCustomAttributes(Type attributeType, bool inherit) 中的第一個(gè)參數attributeType時(shí),將只返回指定類(lèi)型的特性,否則將返回全部特性;第二個(gè)參數指定是否搜索該成員的繼承鏈以查找這些屬性。 class Program { 輸出為: 下面列出應用于 AttributeDemo.DemoClass 的RecordAttribute屬性: 好了,到了這一步,我想將這些數據錄入數據庫中將不再是個(gè)問(wèn)題,我們關(guān)于反射自定義特性的章節也就到此為止了。 轉載地址:http://www.tracefact.net/CLR-and-Framework/Reflection-Part3.aspx |
聯(lián)系客服