C#特性學習與使用(為列舉定義Description)
轉自: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,就到這裡吧。
相關文章
- C# 列舉與位列舉概述C#
- C# 中的“智慧列舉”:如何在列舉中增加行為C#
- rust學習七、列舉Rust
- C學習-列舉(九)
- C#特性學習筆記C#筆記
- 自定義列舉
- c#列舉轉換C#
- 你一定需要知道的高階JAVA列舉特性!Java
- C#學習 陣列(22)C#陣列
- 為什麼建議你使用列舉?
- java中的列舉型別學習Java型別
- Java列舉類學習到進階Java
- C# 使用執行緒池佇列(學習筆記)C#執行緒佇列筆記
- 熱更學習筆記--toLau中lua指令碼對C#中列舉和陣列的訪問筆記指令碼C#陣列
- 在C#中對列舉進行位運算--列舉組合C#
- C語言學習第18篇---巨集定義與使用 / 條件編譯使用分析C語言編譯
- Java總結 Day17 <物件陣列的定義與使用>Java物件陣列
- MYSQL學習與實驗(一)——資料庫定義與操作MySql資料庫
- 物聯網學習教程—列舉型別型別
- C#列舉(一)使用總結以及擴充套件類分享C#套件
- C# 列舉遍歷和轉換C#
- 學習Rust泛型與特性Rust泛型
- Java 定義長度為 0 的陣列 / 空陣列Java陣列
- golang 常量與列舉Golang
- 使用 CliWrap 讓C#中的命令列互動舉重若輕C#命令列
- 【java】【列舉使用技巧】Java
- C++學習 類定義(一)C++
- Pytest系列(21)- allure的特性,@allure.description()、@allure.title()的詳細使用
- CPU使用率的幾個重要指標含義列舉指標
- c#抽象類的定義C#抽象
- 不能使用列舉類作為unordered_map鍵
- YU_C++演算法學習筆記 · 列舉C++演算法筆記
- 轉前端 vue.js 學習筆記-1.2-使用 let 與 const 定義常量變數前端Vue.js筆記變數
- 使用 MapStruct 對映列舉Struct
- Description
- C#學習筆記(一)--- 物件導向的思想和類的定義、物件的建立C#筆記物件
- Python元類與列舉類Python
- JavaScript 遍歷、列舉與迭代JavaScript
- 精通C#學習筆記--C# 與 .NET平臺C#筆記