.net中的Attribute,C#

本文轉載自:http://www.cnblogs.com/zoupeiyang/archive/2011/07/28/2119182.html


    作爲一個.NET開發人員,懂Attribute重要性,用.NET大師Jeffrey Richter的話就是“任何.NET Framework 開發人員都有必要對定製attribute有一個牢靠的掌握”,所以掌握Attitude,這是必須的!

 

  什麼是Attribute(特性)?和Property(屬性)是什麼區別?

 

  我們來看看MSDN中對特性的描述:

 

  Attribute 類將預定義的系統信息或用戶定義的自定義信息與目標元素相關聯。 目標元素可以是程序集、類、構造函數、委託、枚舉、事件、字段、接口、方法、可移植可執行文件模塊、參數、屬性、返回值、結構或其他特性。特性在您編譯代碼時被髮送到元數據中,並可通過運行時反射服務用於公共語言運行時以及任何自定義工具或應用程序。通俗地理解可以這麼表述:你可以通過Attribute將一些額外信息加在一些目標元素上(類,字段,接口等),程序編譯的時候就將這些額外的信息序列化程序集的元數據中,當你運行程序的時候可以通過反射技術從程序集元數據中讀取這些額外信息,並根據這些額外信息決定你程序的行爲。

 

  Attribute和Property有什麼區別?其實這個問題是針對中文背景的開發者而言的,因爲很多中文譯本把Attribute和Property都翻譯成屬性,在這裏爲了區分,我們把Attribute翻譯爲特性,Attribute和Property基本沒有什麼瓜葛,因爲它們是.NET中不同層面的東西,Property就是我們再熟悉不過的定義在類中的屬性,它屬於面向對象理論範疇,而Attribute是編程語言文法層面的東西,其定義在上面一段已經描述。

 

  你使用過.NET定義好的Attribute嗎?

 

  在.NET的基礎類庫中提供了很多定製好的Attribute供開發人員使用,這些定製的Attribute目的的方便開發者在代碼中表達他們的意圖。如下面三個Attribute類都是C#編譯器能夠理解的特性類:

  Obsolete:這個屬性用來標記不再使用的程序實體(如類或方法),每次使用標記爲過時的實體時,會設設定此特性的方法,產生警告或錯誤。

  Conditional:該特性可以標示出某種環境設置下某個方法是否應該被調用。

  Serializable:指示一個類可以序列化。

 

  下面以Obsolete特性的使用爲例,說明Attribute是如何應用它的目標元素的。

 

namespace AttributeDemo
{
    
class Program
    {
        
static void Main(string[] args)
        {
            MyClass myclass 
= new MyClass();
            myclass.OldMethod();
            Console.ReadKey();
        }
    }

    
public class MyClass
    {
        [Obsolete(
"這是一箇舊的方法,請調用新的方法NewMethod")]
        
public void OldMethod()
        {
            Console.WriteLine(
"這是舊方法");
        }

        
public void NewMethod()
        {
            Console.WriteLine(
"這是新方法");
        }
    }
}

 

  調試這段程序的時候會發出警告信息,如下圖所示:

 

  像Obsolete這樣的定製特性,編譯器能夠做出相應處理,如在使用標記了Obsolete特性的方法時會發出警告信息,但如果我們自己定製的Attribute時,編譯器會做什麼處理呢。下面我們自己定義一個Attribute。

 

  我也來定義一個Attribute

 

  爲了符合“公共語言規範”(CLS),定製Attribute必須直接或間接從公共抽象類System.Attribute派生。所以我們前面提到Obsolete、Conditional和Serializable都是派生於Attribute。這裏需要說明下的是自定製的Attribute的命名規範,其規則是“特性名+Attribute”,也就是我們自定製必須以Attribute爲後綴,那麼我們上面提到的三個特性都沒有Attribute爲後綴的呢,原來定義它們的時候都是有Attribute後綴的,如Obsolete是ObsoleteAttribute,只是我們將一個特性應用於某個目標元素時可以將Attribute這個後綴去掉,因爲編譯器會先查找沒有Attribute後綴的特性,如果沒有找到,則會查找加了Attribute後綴的特性名稱。

 

  System.Attribute類的構造器被protected修飾,說明它不能自己實例化,只能被它的派生類調用。它有三個重要的靜態方法,如下:

 

方法名稱 說明
GetCustomAttributes 有多個重載,返回作用於目標的Attribute類實例的數組,也就是返回的類型是Attribute[]
GetCustomAttribute 有多個重載,返回作用於目標的Attribute類的一個實例,如果目標沒有應用任何的Attribute則返回null,如果目標應用了指定的Attribute的多個實例,就拋出一個System.Reflection.AmbiguousMatchException異常。
IsDefined 如果至少有一個指定的Attribute派生類實例作用於目標,就返回true,否則返回false。這個方法效率很高,因爲它不構建Attribute的實例,前面的兩個方法返回都是Attribute實例,也就是需要從元數據中獲取信息來構建實例,耗費性能多

 

 

  通常檢查一個目標元素是否被應用了某個Attribute時,就調用System.Attribute.IsDefined方法,因爲它的性能比GetCustomAttributes和GetCustomAttribute要高,如果需要返回Attribute的實例,則調用GetCustomAttributes或GetCustomAttribute方法。調用這三個方法都會掃描託管模塊的元數據(因爲Attribute是在編譯的時候保存在託管模塊的元數據上的),執行字符串比較來定義指定的Attribute類。這樣的操作對時間性能消耗大,如果需要反覆調用這些方法,可以緩存這些方法的調用結果,也就是把實例保存在全局變量中,不需要每次都掃描和構造實例。

 

  除了System.Attribute類提供的上面的三個靜態方法可以檢查目標元素應用Attribute的情況外,System.Reflection命名空間定義的一些類也允許你檢查一個模塊的元數據的內容,這些類包括Assembly,Module,ParameterInfo,MemberInfo,Type,MethodInfo,ConstrucorInfo,FieldInfo,EventInfo,PropertyInfo等,它們都提供了GetCustomAttributes和IsDefined方法。這些類GetCustomAttributes返回的類型是Object[],而System.Attribute類GetCustomAttributes方法返回的類型是Attribute[]。

 

     下面將分別使用System.Attribute和System.Reflection.Type各自提供的GetCustomAttributes方法獲取Attribute實例提供示例代碼,以讓您有一個更加直觀的認識。

定義一個Attribute

 

public class MyMsgAttribute:Attribute
    {
        
public string Msg { getset; }
        
public MyMsgAttribute(string msg)
        {
            Msg 
= msg;
        }
    }

 

  定義一個類,使自定製的MyMsgAttribute類能夠應用在這個類上,如下:

 

 [MyMsgAttribute("我的自定義Attribute")]
    
public class MyClass
    { 

    }

 

   使用System.Reflection.Type提供的GetCustomAttributes方法獲取MyMsgAttribute類的實例,代碼如下:

 

 var attributes = typeof(MyClass).GetCustomAttributes(typeof(MyMsgAttribute), true);
            MyMsgAttribute myAttribute 
= attributes[0as MyMsgAttribute;
            
if (myAttribute != null)
            {
                Console.WriteLine(myAttribute.Msg);
            }

  

     

   使用System.Attribute提供的GetCustomAttributes方法獲取MyMsgAttribute類的實例,代碼如下:

 

 var attributes2 = Attribute.GetCustomAttributes(typeof(MyClass));
            MyMsgAttribute myAttribute2 
= (MyMsgAttribute)attributes2[0];
            Console.WriteLine(myAttribute2.Msg);

 

  上面兩段代碼輸出的結果都是Msg是屬性值——“我的自定義Attribute”。

 

  經過上面兩段代碼的分析,我們已經知道自定義一個Attribute類,並使這個Attribute應用在一個類上,同時在瞭解了在運行時如何從元數據構造這個Attribute的實例,我們得到Attribute對象,就可以根據這個對象的信息來執行一些邏輯分支代碼,上面只是簡單地輸出Attribute對象Msg屬性值,可見,定製Attribute是非常有用的,因爲它能在運行時決定我們執行不同的邏輯分支代碼。如我們可以通過IsDefined檢查一個類是否應用了SerializableAttribute,從而判斷這個類是否可以用於系列化操作。這裏需要提醒的是,在自定製的Attribute中一般只有屬性和字段成員,不會定義方法。

 

   在我們使用Attribute應用於目標元素的時候,我們會發現一個想象,就是有些Attribute可以應用於類,也可以應用於屬性,如SerializableAttribute,而有些Attribute只能應用於方法這個目標元素,如ConditionalAttribute,爲什麼不同的Attribute會有這種應用目標元素的區別呢?我們查看這兩個Attribute的定義,發現它們本身應用了一個Attribute類,這個Attribute類就是AttributeUsage。因爲Attribute本身就是一個類,所以它是允許應用其它Attribute類的。而AttributeUsage的目的就是限定你的Attribute 所施加的元素的類型,比如限制你的Attribute能夠應用於類還是方法或者屬性上。

 

  AttributeUsage的構造函數有一個參數,這個參數是AttributeTargets的枚舉類型。

  AttributeTargets的枚舉成員名稱說明如下: 
     All 可以對任何應用程序元素應用特性。  
     Assembly 可以對程序集應用特性。  
     Class 可以對類應用特性。  
     Constructor 可以對構造函數應用特性。  
     Delegate 可以對委託應用特性。  
     Enum 可以對枚舉應用特性。  
     Event 可以對事件應用特性。  
     Field 可以對字段應用特性。  
     GenericParameter 可以對泛型參數應用特性。  
     Interface 可以對接口應用特性。  
     Method 可以對方法應用特性。  
     Module 可以對模塊應用特性。 注意Module 指的是可移植的可執行文件(.dll 或 .exe),而非 Visual Basic 標準模塊。

 

  如果你的自定製的Attribute沒有顯式應用AttributeUsage,編譯器會自動給你加上一個默認的AttributeUsage,而這個默認的構造函數參數就是AttributeTargets.All,也就是你的這個自定製Attribute能夠應用下面元素類型爲Assembly | Module | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate, 
ClassMembers = Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface 。

 

  如果你的自定製Attribute只想作用於類和方法,實例代碼如下:

 

 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
    
public class MyMsgAttribute:Attribute
    {
        
public string Msg { getset; }
        
public MyMsgAttribute(string msg)
        {
            Msg 
= msg;
        }
    }

 

  這樣定義的MyMsgAttribute只能應用於類和方法,應用於其它類型的目標元素時編譯的時候會報錯。

 

  AttributeUsage類提供了兩個公共的屬性,AllowMultiple和Inherited。AllowMultiple是使用允許讓多個Attribute實例應用在同一個目標元素上,當我們將AttributeUsage應用於自定製Attribute時,可以指定AllowMultiple屬性值爲True,這樣自定製的Attribute就允許將它的多個實例應用於單個目標元素,如果不將AllowMultiple顯示設爲True,自定製的Attribute只能向一個選定的目標元素應用一次。Inherited屬性指定自定製Attribute應用於基類時,是否同時應用於派生類和重寫的方法。我們用代碼演示AllowMultiple和Inherited的概念:

 

 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple=true,Inherited=true)]
    
public class MyMsgAttribute:Attribute
    {
        
public string Msg { getset; }
        
public MyMsgAttribute(string msg)
        {
            Msg 
= msg;
        }
    }

    [MyMsgAttribute(
"我的自定義Attribute")]
    [MyMsgAttribute(
"也是你的自定義Attribute")]
    
public class MyClass
    {
       
        
public string Name { getset; }

       
        
public string GetName()
        {
            
return Name;
        }
    }

    
public class YourClass : MyClass
    { }
  
static void Main(string[] args)
        {
           var attributes 
= typeof(MyClass).GetCustomAttributes(typeof(MyMsgAttribute), true);
            
foreach (var attribute in attributes)
            {
                MyMsgAttribute myAttribute 
= attribute as MyMsgAttribute;
                
if (myAttribute != null)
                {
                    Console.WriteLine(myAttribute.Msg);
                }
            }

             attributes 
= typeof(YourClass).GetCustomAttributes(typeof(MyMsgAttribute), true);
            
foreach (var attribute in attributes)
            {
                MyMsgAttribute myAttribute 
= attribute as MyMsgAttribute;
                
if (myAttribute != null)
                {
                    Console.WriteLine(myAttribute.Msg);
                }
            }

                       Console.ReadKey();
        }

 

  輸出的結果爲:

 

  總結

 

  我們根據上面文章的分析對自定製Attribute的定義和使用進行總結:

  1、自定製的Attribute必須派生於System.Attribute。

  2、在自定製的Attribute應用AttributeUsageAttribute可以對自定製的Attribute進行應用目標元素、目標元素是否支持應用同一個Attribute多個實例,目標元素應用的Attribute是否能應用於的派生類和派生類的重寫方法等進行控制。

  3、自定製的Attribute是在編譯時保存在模塊的元數據上的,在運行時從元數據讀取信息來構建Attribute實例。

  4、獲取或判斷某個目標元素應用Attribute的信息,可以通過System.Attribute提供的三個鏡頭方法:System.Attribute.IsDefined,

System.Attribute.GetCustomAttributes和System.Attribute.GetCustomAttribute,也可以通過System.Reflection命名空間定義的一些類來檢查一個模塊的元數據的內容,這些類包括Assembly,Module,ParameterInfo,MemberInfo,Type,MethodInfo,ConstrucorInfo,FieldInfo,EventInfo,PropertyInfo等,它們都提供了GetCustomAttributes和IsDefined方法。

  

 

  參考資料:

    Jeffrey Richter CLR Via C#

    微軟 MSDN

    dudu Attribute在.net編程中的應用(一)

 

作者:邊寫邊唱

文章出處:http://www.cnblogs.com/zoupeiyang

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章