轉自:http://blog.csdn.net/nndtdx/article/details/6905802
C#特性
以前的時候,用過C#中的特性,但只是會用,什麼原理,有什麼用這些問題不清楚,今天就騰出時間,學習了一下。
C#中的特性使用Attribute描述。在使用時,就像是java中的批註一樣。不過C#使用中括號。特性用來描述我們的數據。編譯器能夠識別這些特性,以附加信息的形式存放在生成的元數據中,供clr使用。
下邊看一個簡單的應用
- static void Main(string[] args)
- {
- DisplayRunningMsg();
- DisplayDebugMsg();
- Trace("方法執行到結尾了!!");
- Console.Read();
- }
- [DllImport("User32.dll")]
- public static extern int MessageBox(int hParent, string msg, string Caption, int type);
- [Conditional("DEBUG")]
- private static void DisplayRunningMsg()
- {
- Console.WriteLine("This is debug");
- Console.WriteLine("開始運行Main子程序。當前時間是"+DateTime.Now);
- }
- [Conditional("DEBUG")]
- [Obsolete]
- private static void DisplayDebugMsg()
- {
- Console.WriteLine("該方法已經廢棄啦!!!");
- }
DllImport特新允許我們引入一個外部的dll,下邊做一個函數的聲明,我們就可以調用了。
Conditional屬性表示在該種條件下就執行下邊的代碼 所以[Conditional("DEBUG")]此種標識的方法就只有在調試的時候纔會在執行。 [Obsolete]特性標記該方法已經廢棄。
運行上述代碼輸出(在debug模式下)
看的出來程序執行了[Conditional("DEBUG")]標記的方法。如果我們debug改爲release,那麼再次執行
程序並沒有執行上述方法。看的出來,由於特性[Conditional("DEBUG")]標記,是的在release模式下,代碼並沒有運行其標記的函數。那麼,我們就可以利用這個做一個error trace,使其只在debug的模式下輸出當前錯誤信息,包括行號,,方法名,位置等。這裏要用到 stacktrace類。
Ok,說到這裏,你應該對特性有了以最最基本的瞭解。
那麼,究竟什麼是特性呢?
其實特性也是一個類。比如[Conditional("DEBUG")],就是構造了以Conditional對象(調用構造方法public Conditional(string type), 對,DllImport("User32.dll")對應的也有一個類DllImport.
下邊我們自定義一個特性,你就會明白很多。
首先需要定一個一個類 ,該類需要集成Attribute,使其成爲一個特性。.NET約定特性類都已Attribute結尾。然後在該類中定義一下字段和屬性,完成構造。
代碼如下
- [AttributeUsage(AttributeTargets.All,AllowMultiple = true,Inherited = true)]
- class TrackerAttribute:Attribute
- {
- private string opUsername;
- private string opName;
- private DateTime dateTime;
- private string note;
- public TrackerAttribute(string opUsername,string opName,string date)
- {
- this.opUsername = opUsername;
- this.opName = opName;
- this.dateTime = DateTime.Parse(date);
- }
- //位置參數,通過構造函數傳遞值
- public string OpUsername
- {
- get { return opUsername; }
- }
- public string OpName
- {
- get { return opName; }
- }
- public DateTime DateTime
- {
- get { return dateTime; }
- }
- //命名參數,提供set
- public string Note
- {
- get { return note; }
- set { note = value; }
- }
- public override string ToString()
- {
- return "操作人" + opUsername + "操作名" + opName + "時間" + dateTime + "備註" + note;
- }
- }
嗯,對,他和普通的類幾乎沒什麼差別,只不過繼承於Attribute。然後他本身又有一些特性。我們做逐一介紹
我們在類TrackerAttribute 定義了幾個字段,完成了構造函數TrackerAttribute(string opUsername,string opName,stringdate)
那麼我麼在使用的時候就需要寫[Tracker(“opusername”,”opname”,”2011-10-2600:04”,note=”這是備註”)],嗯,是的,使用類型(參數值,參數值)的方法完成了該對象的構造,即調用了該類的構造函數。構造函數裏與字段對應的參數叫做位置參數,因爲寫的時候必須位置一一與源構造函數相同,其他不通過構造函數傳入參數傳遞的,叫做命名參數,使用字段名=字段值 的形式賦值。這樣完成函數構造和一些屬性的賦值。一般情況下,我們將位置參數提供get訪問,而命名參數則提供get和set,因爲位置參數已經能夠同感哦構造函數訪問賦值了。
這個特性類上邊還有幾個特性,AttributeTargets表示當前特性的作用範圍,他是一個位標記的枚舉,比如all,field,method,標記過後,智能在相應的地方做該特性書寫。比如指定枚舉是作用與字段,那麼如果該特性寫在類上邊,就會報錯。
如上,你的特性類就完成了。
這樣你就可以在其他方法上做該特性的標記了。
我們定義了特性,最重要的還是要獲得該特性中的值。下邊是獲得的方法
- Type type = typeof(Program);
- object[] objects = type.GetCustomAttributes(false);
- foreach (var o in objects)
- {
- TrackerAttribute trackerAttribute = o as TrackerAttribute;
- if (trackerAttribute != null)
- Console.WriteLine(trackerAttribute.ToString());
- else
- {
- Console.WriteLine("獲得對象爲空");
- }
- }
type.GetCustomAttributes(false);該方法將會獲得該類上的所有特性標記,返回的是一個object的數組,你可以遍歷,然後轉換爲你的指定特性,訪問相應字段即可。同樣,你也可以通過type.getMethods()[0] 獲得一個methodinfo對象,然後調用該methodinfo對象的GetCustomAttributes方法即可。
介紹瞭如上的這些,我們利用特性,實現爲枚舉增加一個獲得其描述的功能。
例如定義枚舉
MyEnummyenum=MyEnum.TypeA 調用myenum.ToDescription 可以得到字符串 類型A。(轉者注:MyEnum myenum=MyEnum.TypeA)
我們可以想到定一個描述特性,然後在各個枚舉元素上,做該特性的標記,然後提供擴展方法,訪問該特性,取得該特性值。
代碼如下
枚舉定義
- public enum MyType
- {
- [Description("A類型")]
- TypeA,
- [Description("B類型")]
- TypeB,
- [Description("C類型")]
- TypeC
- }
特性類DescriptionAttribute定義如下
- [AttributeUsage(AttributeTargets.Field,AllowMultiple =true,Inherited = true)]
- class DescriptionAttribute:Attribute
- {
- private string description;
- public string Description
- {
- get { return description; }
- }
- public DescriptionAttribute(String description)
- {
- this.description = description;
- }
- }
指定該特性只用於字段,定義DescriptionAttribute(String description)構造函數。
Ok,現在還缺少枚舉的ToDescription方法,C#中的枚舉是不支持定義方法的。
我們可以爲其做一個擴展方法
擴展方法需要一個靜態類,參數前要加this ,同時也指定了被擴展的對象,調用時可使用擴展對像的實例調用,也可以使用該靜態類來調用。詳細內容可參考http://www.cnblogs.com/sunrack/articles/1073759.html
擴展類如下
- public static class Extension
- {
- public static string ToDescription(this MyType myEnum)
- {
- Type type = typeof (MyType);
- FieldInfo info= type.GetField(myEnum.ToString());
- DescriptionAttribute descriptionAttribute= info.GetCustomAttributes(typeof (DescriptionAttribute), true)[0] as DescriptionAttribute;
- if (descriptionAttribute != null)
- return descriptionAttribute.Description;
- else
- return type.ToString();
- }
- }
這樣MyType就多了一個ToDescription的方法,返回的值就是對應的特性值。
在main方法中
MyTypemyType = MyType.TypeB;
Console.WriteLine(myType.ToDescription());
控制檯輸出 B類型 達到了我們想要的效果。
這個方法還是很有用的。
從上邊可以看出,我們如果要爲一些類添加一些附加的信息,1. 這些附加信息在現實意義與該對象並不是具有真正的對象與屬性關係,2. 無法在原來的,裏邊添加字段,或者加入字段後很難處理。這兩種情況之一,都可以使用特性。隨後,在IL中看一下,掉用特性時,編譯器都做了什麼事。晚了,該睡了。
2011/10/31 補:
今天用IL DASM 工具,查看了一下生成的IL代碼,取出其中部分。
枚舉MyType定義如下
- .class public auto ansi sealed caILStudy.MyType
- extends [mscorlib]System.Enum
- {
- .field public specialname rtspecialname int32 value__
- .field public static literal valuetype caILStudy.MyType TypeA = int32(0x00000000)
- .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 41 E7 B1 BB E5 9E 8B 00 00 ) // ...A........
- .field public static literal valuetype caILStudy.MyType TypeB = int32(0x00000001)
- .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 42 E7 B1 BB E5 9E 8B 00 00 ) // ...B........
- .field public static literal valuetype caILStudy.MyType TypeC = int32(0x00000002)
- .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 43 E7 B1 BB E5 9E 8B 00 00 ) // ...C........
- } // end of class caILStudy.MyType
看的出來,枚舉在IL中,任然會被轉換成爲一個類,各個類型是其字段。然而特性的定義是custom instance,我的IL語言功底不行,只能解釋到這裏了。
查看main方法中的代碼
- .method private hidebysig static void Main(string[] args) cil managed
- {
- .entrypoint
- // Code size 14 (0xe)
- .maxstack 1
- .locals init ([0] valuetype caILStudy.MyType myType)
- IL_0000: ldc.i4.1
- IL_0001: stloc.0
- IL_0002: ldloc.0
- IL_0003: call string caILStudy.Extension::ToDescription(valuetype caILStudy.MyType)
- IL_0008: call void [mscorlib]System.Console::WriteLine(string)
- IL_000d: ret
- } // end of method Program::Main
看來,本質上仍然是調用擴展方法,將枚舉參數傳遞進去,輸出結果。Ok,就到這裏吧。