C#反射的特性
如果您現在對反射還不太了解的話,那么可以先看看這篇博文,來粗略的了解一下反射吧。什么是反射
反射特性(Attribute)
1. C#內置特性介紹
特性是一個對象,它可以加載到程序集及程序集的對象中,這些對象包括 程序集本身、模塊、類、接口、結構、構造函數、方法、方法參數等,加載了特性的對象稱作特性的目標。特性是為程序添加元數據(描述數據的數據)的一種機制,通過它可以給編譯器提供指示或者提供對數據的說明。
注意:特性的英文名稱叫做Attribute,在有的書中,將它翻譯為“屬性”;另一些書中,將它翻譯為“特性”;由于通常我們將含有get和/或set訪問器的類成員稱為“屬性”(英文Property),所以本文中我將使用“特性”這個名詞,以區分“屬性”(Property)。??
上面這個提示是在VS中的,大家在編程的過程中應該有遇到過的。
下面我們就引入第一個特性
1.1 System.ObsoleteAttribute 特性
我們通過如圖示這個例子來看一下特性是如何解決上面的問題:我們可以給舊的SendMsg()方法上面加上Obsolete特性來告訴編譯器這個方法已經過時,然后當編譯器發現當程序中有地方在使用這個用Obsolete標記過的方法時,就會給出一個警告信息。
namespace TestObsolete {class Program{public class Message { }public class TestClass{// 添加Obsolete特性[Obsolete("請使用新的SendMsg(Message msg)重載方法")]public static void ShowMsg(){Console.WriteLine("這是舊的SendMsg()方法");}public static void ShowMsg(Message msg){Console.WriteLine("新SendMsg()方法");}}static void Main(string[] args){TestClass.ShowMsg();TestClass.ShowMsg(new Message());}} }?簡單的代碼如上。現在運行這段代碼,我們會發現編譯器給出了一個警告:警告CS0618: “Attribute.TestClass.ShowMsg()”已過時:“請使用新的SendMsg(Message msg)重載方法”。通過使用特性,我們可以看到編譯器給出了警告信息,告訴客戶程序存在一個新的方法可供使用,這樣,程序員在看到這個警告信息后,便會考慮使用新的SendMsg()方法。
1.2 特性的使用方法
通過上面的例子,我們已經大致看到特性的使用方法:首先是有一對方括號“[]”,在左方括號“[”后緊跟特性的名稱,比如Obsolete,隨后是一個圓括號“()”。和普通的類不同,這個圓括號不光可以寫入構造函數的參數,還可以給類的屬性賦值,在Obsolete的例子中,僅傳遞了構造函數參數。
注意:
實際上,當你用鼠標框選住Obsolete,然后按下F12轉到定義,會發現它的全名是ObsoleteAttribute,繼承自Attribute類。但是這里卻僅用Obsolete來標記方法,這是.Net的一個約定,所有的特性應該均以Attribute來結尾,在為對象標記特性時如果沒有添加Attribute,編譯器會自動尋找帶有Attribute的版本。
?
使用構造函數參數,參數的順序必須同構造函數聲明時的順序相同,所有在特性中也叫位置參數(Positional Parameters),與此相應,屬性參數也叫做命名參數(Named Parameters)。在下面會詳細說明。
2.自定義特性(Custom Attributes)
2.1范例介紹
如果不能自己定義一個特性并使用它,我想你怎么也不能很好的理解特性,我們現在就自己構建一個特性。假設我們有這樣一個很常見的需求:我們在創建或者更新一個類文件時,需要說明這個類是什么時候、由誰創建的,在以后的更新中還要說明在什么時候由誰更新的,可以記錄也可以不記錄更新的內容,以往你會怎么做呢?是不是像這樣在類的上面給類添加注釋:
[Record("更新", "Leo", "2013-3-20", Memo = "修改……")][Record("創建", "Amy", "2013-3-10")][Record("更新", "aehyok", "2013-3-18")]public class DemoClass{public override string ToString(){return "This is a demo class";}}?這樣的的確確是可以記錄下來,但是如果有一天我們想將這些記錄保存到數據庫中作以備份呢?你是不是要一個一個地去查看源文件,找出這些注釋,再一條條插入數據庫中呢?
通過上面特性的定義,我們知道特性可以用于給類型添加元數據,這些元數據可以用于描述類型。那么在此處,特性應該會派上用場。那么在本例中,元數據應該是:注釋類型(“更新”或者“創建”),修改人,日期,備注信息(可有可無)。而特性的目標類型是DemoClass類。
按照對于附加到DemoClass類上的元數據的理解,我們先創建一個封裝了元數據的類RecordAttribute:
public class RecordAttribute{private string recordType; // 記錄類型:更新/創建private string author; // 作者private DateTime date; // 更新/創建 日期// 構造函數,構造函數的參數在特性中也稱為“位置參數”。public RecordAttribute(string recordType, string author, string date){this.recordType = recordType;this.author = author;this.date = Convert.ToDateTime(date);}// 對于位置參數,通常只提供get訪問器public string RecordType { get { return recordType; } }public string Author { get { return author; } }public DateTime Date { get { return date; } }// public string Memo { get; set; }}?這個類不光看上去,實際上也和普通的類沒有任何區別,顯然不能它因為名字后面跟了個Attribute就搖身一變成了特性。那么怎樣才能讓它稱為特性并應用到一個類上面呢?進行下一步之前,我們看看.Net內置的特性Obsolete是如何定義的:
// 摘要:// 標記不再使用的程序元素。無法繼承此類。[Serializable][ComVisible(true)][AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]public sealed class ObsoleteAttribute : Attribute{// 摘要:// 使用默認屬性初始化 System.ObsoleteAttribute 類的新實例。public ObsoleteAttribute();//// 摘要:// 使用指定的變通方法消息初始化 System.ObsoleteAttribute 類的新實例。//// 參數:// message:// 描述可選的變通方法的文本字符串。public ObsoleteAttribute(string message);//// 摘要:// 使用變通方法消息和布爾值初始化 System.ObsoleteAttribute 類的新實例,該布爾值指示是否將使用已過時的元素視為錯誤。//// 參數:// message:// 描述可選的變通方法的文本字符串。//// error:// 指示是否將使用已過時的元素視為錯誤的布爾值。public ObsoleteAttribute(string message, bool error);// 摘要:// 獲取指示編譯器是否將使用已過時的程序元素視為錯誤的布爾值。//// 返回結果:// 如果將使用已過時的元素視為錯誤,則為 true;否則為 false。默認為 false。public bool IsError { get; }//// 摘要:// 獲取變通方法消息,包括對可選程序元素的說明。//// 返回結果:// 變通方法文本字符串。public string Message { get; }} }?2.2添加特性的格式(位置參數和命名參數)
首先,我們應該發現,它繼承自Attribute類,這說明我們的RecordAttribute也應該繼承自Attribute類。
其次,我們發現在這個特性的定義上,又用了三個特性去描述它。這三個特性分別是:Serializable、AttributeUsage 和 ComVisible。Serializable特性應該主要是用來序列化用的,ComVisible簡單來說是“控制程序集中個別托管類型、成員或所有類型對 COM 的可訪問性”(微軟給的定義)。這里我們應該注意到:特性本身就是用來描述數據的元數據,而這三個特性又用來描述特性,所以它們可以認為是“元數據的元數據”(元元數據:meta-metadata)。
因為我們需要使用“元元數據”去描述我們定義的特性 RecordAttribute,所以現在我們需要首先了解一下“元元數據”。這里應該記得“元元數據”也是一個特性,大多數情況下,我們只需要掌握 AttributeUsage就可以了,所以現在就研究一下它。我們首先看上面AttributeUsage是如何加載到ObsoleteAttribute特性上面的。
[AttributeUsage(8192, Inherited = false)]?
然后我們看一下AttributeUsage的定義:
// 摘要:// 指定另一特性類的用法。無法繼承此類。[Serializable][ComVisible(true)][AttributeUsage(AttributeTargets.Class, Inherited = true)]public sealed class AttributeUsageAttribute : Attribute{// 摘要:// 用指定的 System.AttributeTargets、System.AttributeUsageAttribute.AllowMultiple// 值和 System.AttributeUsageAttribute.Inherited 值列表初始化 System.AttributeUsageAttribute// 類的新實例。//// 參數:// validOn:// 使用按位“或”運算符組合的一組值,用于指示哪些程序元素是有效的。public AttributeUsageAttribute(AttributeTargets validOn);// 摘要:// 獲取或設置一個布爾值,該值指示能否為一個程序元素指定多個指示特性實例。//// 返回結果:// 如果允許指定多個實例,則為 true;否則為 false。默認為 false。public bool AllowMultiple { get; set; }//// 摘要:// 獲取或設置一個布爾值,該值指示指示的特性能否由派生類和重寫成員繼承。//// 返回結果:// 如果該特性可由派生類和重寫成員繼承,則為 true,否則為 false。默認為 true。public bool Inherited { get; set; }//// 摘要:// 獲取一組值,這組值標識指示的特性可應用到的程序元素。//// 返回結果:// 一個或多個 System.AttributeTargets 值。默認為 All。public AttributeTargets ValidOn { get; }}?可以看到,它有一個構造函數,這個構造函數含有一個AttributeTargets類型的位置參數(Positional Parameter)。注意ValidOn屬性不是一個命名參數,因為它不包含set訪問器。
這里大家一定疑惑為什么會這樣劃分參數,這和特性的使用是相關的。假如AttributeUsageAttribute 是一個普通的類,我們一定是這樣使用的:
// 實例化一個 AttributeUsageAttribute 類 AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class) ; usage.AllowMultiple = true; // 設置AllowMutiple屬性 usage.Inherited = false;// 設置Inherited屬性?但是,特性只寫成一行代碼,然后緊靠其所應用的類型(目標類型),那么怎么辦呢?微軟的軟件工程師們就想到了這樣的辦法:不管是構造函數的參數 還是 屬性,統統寫到構造函數的圓括號中,對于構造函數的參數,必須按照構造函數參數的順序和類型;對于屬性,采用“屬性=值”這樣的格式,它們之間用逗號分隔。于是上面的代碼就減縮成了這樣:
[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]?
可以看出,AttributeTargets.Class是構造函數參數(位置參數),而AllowMutiple 和 Inherited實際上是屬性(命名參數)。命名參數是可選的。將來我們的RecordAttribute的使用方式于此相同。(為什么管他們叫參數,我猜想是因為它們的使用方式看上去更像是方法的參數吧。)
假設現在我們的RecordAttribute已經OK了,則它的使用應該是這樣的:
[Record("創建", "Amy", "2013-3-10","創建Test")]public class DemoClass?其中recordType, author 和 date 是位置參數,Memo是命名參數。
2.3 AttributeTargets 位標記
從AttributeUsage特性的名稱上就可以看出它用于描述特性的使用方式。具體來說,首先應該是其所標記的特性可以應用于哪些類型或者對象。從上面的代碼,我們看到AttributeUsage特性的構造函數接受一個 AttributeTargets 類型的參數,那么我們現在就來了解一下AttributeTargets。
AttributeTargets 是一個位標記,它定義了特性可以應用的類型和對象。
// 摘要:// 指定可以對它們應用特性的應用程序元素。[Serializable][Flags][ComVisible(true)]public enum AttributeTargets{// 摘要:// 可以對程序集應用特性。Assembly = 1,//// 摘要:// 可以對模塊應用特性。Module = 2,//// 摘要:// 可以對類應用特性。Class = 4,//// 摘要:// 可以對結構應用特性,即值類型。Struct = 8,//// 摘要:// 可以對枚舉應用特性。Enum = 16,//// 摘要:// 可以對構造函數應用特性。Constructor = 32,//// 摘要:// 可以對方法應用特性。Method = 64,//// 摘要:// 可以對屬性應用特性。Property = 128,//// 摘要:// 可以對字段應用特性。Field = 256,//// 摘要:// 可以對事件應用特性。Event = 512,//// 摘要:// 可以對接口應用特性。Interface = 1024,//// 摘要:// 可以對參數應用特性。Parameter = 2048,//// 摘要:// 可以對委托應用特性。Delegate = 4096,//// 摘要:// 可以對返回值應用特性。ReturnValue = 8192,//// 摘要:// 可以對泛型參數應用特性。GenericParameter = 16384,//// 摘要:// 可以對任何應用程序元素應用特性。All = 32767,}?現在應該不難理解為什么上面我范例中用的是:
[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]?而ObsoleteAttribute特性上加載的 AttributeUsage是這樣的:
[AttributeUsage(8192, Inherited = false)]?因為AttributeUsage是一個位標記,所以可以使用按位或“|”來進行組合。所以,當我們這樣寫時:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)?意味著既可以將特性應用到類上,也可以應用到接口上。
注意:這里存在著兩個特例:觀察上面AttributeUsage的定義,說明特性還可以加載到程序集Assembly和模塊Module上,而這兩個屬于我們的編譯結果,在程序中并不存在這樣的類型,我們該如何加載呢?可以使用這樣的語法:[assembly:SomeAttribute(parameter list)],另外這條語句必須位于程序語句開始之前。
2.4 Inherited 和 AllowMutiple屬性
AllowMutiple 屬性用于設置該特性是不是可以重復地添加到一個類型上(默認為false),就好像這樣:
[Record("更新", "Leo", "2013-3-20", Memo = "修改……")][Record("更新", "aehyok", "2013-3-18")][Record("創建", "Amy", "2013-3-10")]public class DemoClass{?所以,我們必須顯示的將AllowMutiple設置為True。
Inherited 就更復雜一些了,假如有一個類繼承自我們的DemoClass,那么當我們將RecordAttribute添加到DemoClass上時,DemoClass的子類也會獲得該特性。而當特性應用于一個方法,如果繼承自該類的子類將這個方法覆蓋,那么Inherited則用于說明是否子類方法是否繼承這個特性。
在我們的例子中,將 Inherited 設為false。
2.5 實現 RecordAttribute
現在實現RecordAttribute應該是非常容易了,對于類的主體不需要做任何的修改,我們只需要讓它繼承自Attribute基類,同時使用AttributeUsage特性標記一下它就可以了(假定我們希望可以對類和方法應用此特性):
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]public class RecordAttribute:Attribute{private string recordType; // 記錄類型:更新/創建private string author; // 作者private DateTime date; // 更新/創建 日期// 構造函數,構造函數的參數在特性中也稱為“位置參數”。public RecordAttribute(string recordType, string author, string date){this.recordType = recordType;this.author = author;this.date = Convert.ToDateTime(date);}// 對于位置參數,通常只提供get訪問器public string RecordType { get { return recordType; } }public string Author { get { return author; } }public DateTime Date { get { return date; } }// 構建一個屬性,在特性中也叫“命名參數”public string Memo { get; set; }}?2.6 使用 RecordAttribute
[Record("更新", "Leo", "2013-3-20", Memo = "修改……")][Record("更新", "aehyok", "2013-3-18")][Record("創建", "Amy", "2013-3-10")]public class DemoClass{public override string ToString(){return "This is a demo class";}}class Program{static void Main(string[] args){DemoClass demo = new DemoClass();Console.WriteLine(demo.ToString());?這段程序簡單地在屏幕上輸出一個“This is a demo class”。我們的屬性也好像使用“//”來注釋一樣對程序沒有任何影響,實際上,我們添加的數據已經作為元數據添加到了程序集中。
3.使用反射查看自定義特性
利用反射來查看 自定義特性信息 與 查看其他信息 類似,首先基于類型(本例中是DemoClass)獲取一個Type對象,然后調用Type對象的GetCustomAttributes()方法,獲取應用于該類型上的特性。當指定GetCustomAttributes(Type attributeType, bool inherit) 中的第一個參數attributeType時,將只返回指定類型的特性,否則將返回全部特性;第二個參數指定是否搜索該成員的繼承鏈以查找這些屬性。
Type t = typeof(DemoClass);Console.WriteLine("下面列出應用于 {0} 的RecordAttribute屬性:", t);// 獲取所有的RecordAttributes特性object[] records = t.GetCustomAttributes(typeof(RecordAttribute), false);foreach (RecordAttribute record in records){Console.WriteLine(" {0}", record);Console.WriteLine(" 類型:{0}", record.RecordType);Console.WriteLine(" 作者:{0}", record.Author);Console.WriteLine(" 日期:{0}", record.Date.ToShortDateString());if (!String.IsNullOrEmpty(record.Memo)){Console.WriteLine(" 備注:{0}", record.Memo);}}?下面來看一下最后的執行效果
?
這是實例代碼下載連接示例代碼
?
轉載于:https://www.cnblogs.com/aehyok/archive/2013/03/27/2969010.html
總結