C#特性學習與使用(為列舉定義Description)

pamxy發表於2013-12-02

轉自:http://blog.csdn.net/nndtdx/article/details/6905802

C#特性

以前的時候,用過C#中的特性,但只是會用,什麼原理,有什麼用這些問題不清楚,今天就騰出時間,學習了一下。

C#中的特性使用Attribute描述。在使用時,就像是java中的批註一樣。不過C#使用中括號。特性用來描述我們的資料。編譯器能夠識別這些特性,以附加資訊的形式存放在生成的後設資料中,供clr使用。

下邊看一個簡單的應用

  1. static void Main(string[] args)  
  2.  {  
  3.            DisplayRunningMsg();  
  4.             DisplayDebugMsg();  
  5.             Trace("方法執行到結尾了!!");  
  6.             Console.Read();  
  7.   
  8. }  
  9.  [DllImport("User32.dll")]  
  10.  public static extern int MessageBox(int hParent, string msg, string Caption, int type);  
  11.         [Conditional("DEBUG")]  
  12.         private  static  void DisplayRunningMsg()  
  13.         {  
  14.             Console.WriteLine("This is debug");  
  15.             Console.WriteLine("開始執行Main子程式。當前時間是"+DateTime.Now);  
  16.         }  
  17.   
  18.         [Conditional("DEBUG")]  
  19.         [Obsolete]  
  20.         private  static  void DisplayDebugMsg()  
  21.         {  
  22.             Console.WriteLine("該方法已經廢棄啦!!!");  
  23.         }  

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結尾。然後在該類中定義一下欄位和屬性,完成構造。

程式碼如下

  1. [AttributeUsage(AttributeTargets.All,AllowMultiple = true,Inherited = true)]  
  2.     class TrackerAttribute:Attribute  
  3.     {  
  4.           
  5.         private string opUsername;  
  6.         private string opName;  
  7.         private DateTime dateTime;  
  8.         private string note;  
  9.   
  10.         public  TrackerAttribute(string  opUsername,string  opName,string date)  
  11.         {  
  12.             this.opUsername = opUsername;  
  13.             this.opName = opName;  
  14.             this.dateTime = DateTime.Parse(date);  
  15.         }  
  16.   
  17.         //位置引數,通過建構函式傳遞值  
  18.         public string  OpUsername  
  19.         {  
  20.             get { return opUsername; }  
  21.         }  
  22.   
  23.         public  string  OpName  
  24.         {  
  25.             get { return opName; }  
  26.         }  
  27.   
  28.         public  DateTime DateTime  
  29.         {  
  30.             get { return dateTime; }  
  31.         }  
  32.   
  33.         //命名引數,提供set  
  34.         public string  Note  
  35.         {  
  36.             get { return note; }  
  37.             set { note = value; }  
  38.         }  
  39.   
  40.         public override string ToString()  
  41.         {  
  42.             return "操作人" + opUsername + "操作名" + opName + "時間" + dateTime + "備註" + note;  
  43.         }  
  44.     }  

嗯,對,他和普通的類幾乎沒什麼差別,只不過繼承於Attribute。然後他本身又有一些特性。我們做逐一介紹

我們在類TrackerAttribute 定義了幾個欄位,完成了建構函式TrackerAttribute(string opUsername,string  opName,stringdate)

那麼我麼在使用的時候就需要寫[Tracker(“opusername”,”opname”,”2011-10-2600:04”,note=”這是備註”)],嗯,是的,使用型別(引數值,引數值)的方法完成了該物件的構造,即呼叫了該類的建構函式。建構函式裡與欄位對應的引數叫做位置引數,因為寫的時候必須位置一一與源建構函式相同,其他不通過建構函式傳入引數傳遞的,叫做命名引數使用欄位名=欄位值 的形式賦值。這樣完成函式構造和一些屬性的賦值。一般情況下,我們將位置引數提供get訪問,而命名引數則提供get和set,因為位置引數已經能夠同感哦建構函式訪問賦值了。

這個特性類上邊還有幾個特性,AttributeTargets表示當前特性的作用範圍,他是一個位標記的列舉,比如all,field,method,標記過後,智慧在相應的地方做該特性書寫。比如指定列舉是作用與欄位,那麼如果該特性寫在類上邊,就會報錯。

如上,你的特性類就完成了。

這樣你就可以在其他方法上做該特性的標記了。

我們定義了特性,最重要的還是要獲得該特性中的值。下邊是獲得的方法

  1. Type type = typeof(Program);  
  2.             object[] objects = type.GetCustomAttributes(false);  
  3.             foreach (var o in objects)  
  4.             {  
  5.                 TrackerAttribute trackerAttribute = o as TrackerAttribute;  
  6.                 if (trackerAttribute != null)  
  7.                     Console.WriteLine(trackerAttribute.ToString());  
  8.                 else  
  9.                 {  
  10.                     Console.WriteLine("獲得物件為空");  
  11.                 }  
  12.             }  

type.GetCustomAttributes(false);該方法將會獲得該類上的所有特性標記,返回的是一個object的陣列,你可以遍歷,然後轉換為你的指定特性,訪問相應欄位即可。同樣,你也可以通過type.getMethods()[0] 獲得一個methodinfo物件,然後呼叫該methodinfo物件的GetCustomAttributes方法即可。

 

介紹瞭如上的這些,我們利用特性,實現為列舉增加一個獲得其描述的功能。

例如定義列舉

MyEnummyenum=MyEnum.TypeA 呼叫myenum.ToDescription 可以得到字串 型別A。(轉者注:MyEnum myenum=MyEnum.TypeA)

我們可以想到定一個描述特性,然後在各個列舉元素上,做該特性的標記,然後提供擴充套件方法,訪問該特性,取得該特性值。

程式碼如下

列舉定義

  1. public enum MyType  
  2.     {  
  3.         [Description("A型別")]  
  4.         TypeA,  
  5.         [Description("B型別")]  
  6.         TypeB,  
  7.         [Description("C型別")]  
  8.         TypeC  
  9.     }  

特性類DescriptionAttribute定義如下

  1. [AttributeUsage(AttributeTargets.Field,AllowMultiple =true,Inherited = true)]  
  2.   class DescriptionAttribute:Attribute  
  3.   {  
  4.       private string description;  
  5.       public  string Description  
  6.       {  
  7.           get { return description; }  
  8.       }  
  9.   
  10.       public  DescriptionAttribute(String description)  
  11.       {  
  12.           this.description = description;  
  13.       }  
  14.   }  

指定該特性只用於欄位,定義DescriptionAttribute(String description)建構函式。

Ok,現在還缺少列舉的ToDescription方法,C#中的列舉是不支援定義方法的。

我們可以為其做一個擴充套件方法

擴充套件方法需要一個靜態類,引數前要加this ,同時也指定了被擴充套件的物件,呼叫時可使用擴充套件對像的例項呼叫,也可以使用該靜態類來呼叫。詳細內容可參考http://www.cnblogs.com/sunrack/articles/1073759.html

 

擴充套件類如下

  1. public  static class Extension  
  2.    {  
  3.   
  4.        public  static string ToDescription(this MyType myEnum)  
  5.        {  
  6.            Type type = typeof (MyType);  
  7.            FieldInfo info= type.GetField(myEnum.ToString());  
  8.             DescriptionAttribute descriptionAttribute= info.GetCustomAttributes(typeof (DescriptionAttribute), true)[0] as DescriptionAttribute;  
  9.             if (descriptionAttribute != null)  
  10.                 return descriptionAttribute.Description;  
  11.             else  
  12.                 return type.ToString();  
  13.        }  
  14.    }  


這樣MyType就多了一個ToDescription的方法,返回的值就是對應的特性值。

在main方法中

            MyTypemyType = MyType.TypeB;

            Console.WriteLine(myType.ToDescription());

控制檯輸出 B型別 達到了我們想要的效果。

這個方法還是很有用的。

 

從上邊可以看出,我們如果要為一些類新增一些附加的資訊,1. 這些附加資訊在現實意義與該物件並不是具有真正的物件與屬性關係,2. 無法在原來的,裡邊新增欄位,或者加入欄位後很難處理。這兩種情況之一,都可以使用特性。隨後,在IL中看一下,掉用特性時,編譯器都做了什麼事。晚了,該睡了。


2011/10/31 補:

 

今天用IL DASM 工具,檢視了一下生成的IL程式碼,取出其中部分。

列舉MyType定義如下

  1. .class public auto ansi sealed caILStudy.MyType  
  2.        extends [mscorlib]System.Enum  
  3. {  
  4.   .field public specialname rtspecialname int32 value__  
  5.   .field public static literal valuetype caILStudy.MyType TypeA = int32(0x00000000)  
  6.   .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 41 E7 B1 BB E5 9E 8B 00 00 )             // ...A........  
  7.   .field public static literal valuetype caILStudy.MyType TypeB = int32(0x00000001)  
  8.   .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 42 E7 B1 BB E5 9E 8B 00 00 )             // ...B........  
  9.   .field public static literal valuetype caILStudy.MyType TypeC = int32(0x00000002)  
  10.   .custom instance void caILStudy.DescriptionAttribute::.ctor(string) = ( 01 00 07 43 E7 B1 BB E5 9E 8B 00 00 )             // ...C........  
  11. } // end of class caILStudy.MyType  

看的出來,列舉在IL中,任然會被轉換成為一個類,各個型別是其欄位。然而特性的定義是custom instance,我的IL語言功底不行,只能解釋到這裡了。

檢視main方法中的程式碼


  1. .method private hidebysig static void  Main(string[] args) cil managed  
  2.  {  
  3.    .entrypoint  
  4.    // Code size       14 (0xe)  
  5.    .maxstack  1  
  6.    .locals init ([0] valuetype caILStudy.MyType myType)  
  7.    IL_0000:  ldc.i4.1  
  8.    IL_0001:  stloc.0  
  9.    IL_0002:  ldloc.0  
  10.    IL_0003:  call       string caILStudy.Extension::ToDescription(valuetype caILStudy.MyType)  
  11.    IL_0008:  call       void [mscorlib]System.Console::WriteLine(string)  
  12.    IL_000d:  ret  
  13.  } // end of method Program::Main  

看來,本質上仍然是呼叫擴充套件方法,將列舉引數傳遞進去,輸出結果。Ok,就到這裡吧。


相關文章