C#中的attribute

轉自:http://www.cnblogs.com/-dawn/archive/2012/09/29/2708053.html


介紹

Attributes是一種新的描述信息,我們既可以使用attributes來定義設計期信息(例如幫助文件,文檔的URL),還可以用attributes定義運行時信息(例如,使XML中的元素與類的成員字段關聯起來)。我們也可以用attributes來創建一個“自描述”的組件。在這篇指南中我們將明白怎麼創建屬性並將其綁定至各種語言元素上,另外我們怎樣在運行時環境下獲取到attributes的一些信息。

定義

MSDN 中做如下定義(ms-help://MS.MSDNQTR.2002APR.1033/csspec/html/vclrfcsh ARP spec_17_2.htm)

"An attribute is a piece of additional declarative information that is specified for a declaration."

使用預定義Attributes

c#中已有一小組預定義的attributes,在我們學習怎樣創建自定義attributes前,先來了解下在我們的代碼中使用那些預定義的attributes.

using System;
publicclass AnyClass 
{
    [Obsolete( "Don't use Old method, use New method", true)]
staticvoid Old( ) { }
staticvoid New( ) { }
publicstaticvoid Main( ) 
    {
        Old( );
    }
}


仔細看下該實例,在該實例中我們用到了”Obsolete”attribute,它標記了一個不該再被使用的語言元素譯者注:這裏的元素爲方法,該屬性的第一個參數是string類型,它解釋爲什麼該元素被荒棄,以及我們該使用什麼元素來代替它。實際中,我們可以書寫任何其它文本來代替這段文本。第二個參數是告訴編譯器把依然使用這被標識的元素視爲一種錯誤,這就意味着編譯器會因此而產生一個警告。

當我們試圖編譯上面的上面的程序,我們會得到如下錯誤:

AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'

開發自定義Attributes

現在我們即將瞭解怎麼開發自定義的attributes。這兒有個小小處方,有它我們就可以學會創建自定義的attributes

C#中,我們的attribute類都派生於System.Attribute( A class that derives from the abstract class System.Attribute, whether directly or indirectly, is an attribute class. The declaration of an attribute class defines a new kind of attribute that can be placed on a declaration ) ,我們就這麼行動吧。




using System;

publicclass HelpAttribute : Attribute

{

}

不管你是否相信我,就這樣我們就已經創建了一個自定義 attribute。現在就可以用它來裝飾我們的類了,就像我們使用obsolete attribute一樣。


[Help()]

publicclass AnyClass

{

}


注意:按慣例我們是用”Attribute“作爲attribute類名的後綴,然而,當我們當我們把attribute綁定到某語言元素時,是不包含“Attribute“後綴的。編譯器首先在System.Attribute的繼承類中查找該attribute,如果沒有找到,編譯器會把“Attribute“追加到該attribute的名字後面,然後查找它。

但是迄今爲止,該attribute沒有任何用處。爲了使它有點用處,讓我們在它裏面加點東西吧。

using System;
publicclass HelpAttribute : Attribute
{
public HelpAttribute(String Descrition_in)
    {
this.description = Description_in;
    }
protected String description;
public String Description 
{
    get
    {
        returnthis.description;
    }  
}
[Help( "this is a do-nothing class")]
publicclass AnyClass
{
}

在上面的例子中,我們在attribute類中添加了一個屬性,在最後一節中,我們將在運行時查詢該屬性。

定義或控制自定義Attribute的用法

AttributeUsage類是另一預定義類譯者注:attribute類本身用這個atrribute System.AttributeUsage來標記,它將幫助我們控制我們自定義attribute的用法,這就是,我們能爲自定義的attribute類定義attributes

它描述了一個自定義attribute類能被怎樣使用。

AttributeUsage提供三個屬性,我們能將它們放置到我們的自定義attribute類上,第一個特性是:

ValidOn

通過這個屬性,我們能指定我們的自定義attribute可以放置在哪些語言元素之上。這組我們能把自定義attribute類放置其上的語言元素被放在枚舉器AttributeTargets中。我們可以使用bitwise( 譯者注:這個詞不知道怎麼翻譯好,但他的意思是可以這麼用 [AttributeUsage (( AttributeTargets)4 , AllowMultiple =false, Inherited =false )], 4代表就是class元素,其它諸如 1 代表“ assembly ”, 16383 代表“ all ”等等 或者”.”操做符綁定幾個AttributeTargets值。(譯者注:默認值爲AttributeTargets.All)

AllowMultiple

該屬性標識我們的自定義attribte能在同一語言元素上使用多次。譯者注:該屬性爲bool類型,默認值爲false,意思就是該自定義attribute在同一語言元素上只能使用一次


Inherited

我們可以使用該屬性來控制我們的自定義attribute類的繼承規則。該屬性標識我們的自定義attribute是否可以由派生類繼承。((譯者注:該屬性爲bool類型,默認值爲false,意思是不能繼承)

讓我們來做點實際的東西吧,我們將把AttributeUsageattribute 放置在我們的help attribute 上並在它的幫助下,我們來控制help attribute的用法。

using System;
[AttributeUsage(AttributeTargets.Class, AllowMultiple  =false, Inherited =false )]
publicclass HelpAttribute : Attribute
{
    public HelpAttribute(String Description_in)
    {
        this.description = Description_in;
    }
protected String description;
public String Description
{
    get
    {
        returnthis.description;
    }            
}



首先我們注意 AttributeTargets.Class. 它規定這個help attribute 只能放置在語言元素”class”之上。這就意味着,下面的代碼將會產生一個錯誤。

AnyClass.cs: Attribute 'Help' is not valid on this declaration type.
It is valid on 'class' declarations only.

現在試着把它綁定到方法。

[Help("this is a do-nothing class")]
publicclass AnyClass
{
    [Help( "this is a do-nothing method")]    //error
    publicvoid AnyMethod()
    {
    }
}


我們可以使用 AttributeTargets.All來允許Help attribute 可以放置在任何預定義的語言元素上,那些可能的語言元素如下:

  • Assembly,

  • Module,

  • Class,

  • Struct,

  • Enum,

  • Constructor,

  • Method,

  • Property,

  • Field,

  • EVE nt,

  • Interface,

  • Parameter,

  • Delegate,

  • All = Assembly | Module | Class | Struct | Enum | Constructor | Method | Property | Field | EVE nt | Interface | Parameter | Delegate,

  • ClassMembers = Class | Struct | Enum | Constructor | Method | Property | Field | EVE nt | Delegate | Interface )

  • ~ 現在考慮下AllowMultiple = false. 這就規定該attribute 不能在同一語言元素上放置多次.




[Help("this is a do-nothing class")]

[Help(
"it contains a do-nothing method")]

publicclass AnyClass

{

   [Help(
"this is a do-nothing method")]        //error

publicvoid AnyMethod()

   {

   }

}

它產生了一個編譯錯誤:

AnyClass.cs: Duplicate 'Help' attribute

Ok!現在我們該討論下最後那個屬性了,”Inherited”, 指出當把該attribute放置於一個基類之上,是否派生類也繼承了該attribute。如果綁定至某個attribute類的”Inherited”被設爲true,那麼該attribute就會被繼承,然而如果綁定至某個attribute類的”Inherited”被設爲false或者沒有定義,那麼該attribute就不會被繼承。

讓我們假設有如下的類關係。




[Help("BaseClass")]  

publicclass Base

{

}



publicclass Derive :  Base

{

}

我們有四種可能的綁定 :

  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]

  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]

  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]

  • [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]

第一種情況

如果我們查詢(我們將在後面來了解如何在運行時來查詢attributes)派生類中的help attribute,我們將不可能查詢到因爲”Inherited”被設爲了false

第二種情況

第二種情況沒有什麼不同,因爲其”Inherited”也被設爲了false

第三種情況

爲了解釋第三種和第四種情況,讓我們爲派生類也綁定同一attribute


[Help("BaseClass")]  

publicclass Base

{

}

[Help(
"DeriveClass")]  

publicclass Derive :  Base

{

}

現在我們查詢相關的 help attribute ,我們將僅僅可以得到派生類的attribute,爲什麼這樣是因爲help attribute雖然允許被繼承,但不能多次在同一語言元素上使用,所以基類中的help attribute被派生類的help attribute 重寫了。

第四種情況

在第四種情況中,當我們查詢派生類的help attribute 時,我們可以得到兩個attributes,當然是因爲help attribute既允許被繼承,又允許在同一語言元素上多次使用的結果。

注意:AttributeUsageattribute 僅應用在那種是System.Attribute派生的attriubte類而且綁定值該attriubte類的AllowMultipleInherited均爲false上纔是有效的。

可選參數vs. 命名參數

可選參數是attribute類構造函數的參數。它們是強制的,必須在每次在attribute綁定至某語言元素時提供一個值。而另一方面,命名參數倒是真正的可選參數,不是在attribute構造函數的參數。

爲了更加詳細的解釋,讓我們在Help類中添加另外的屬性。


[AttributeUsage(AttributeTargets.Class, AllowMultiple =false,

Inherited  
=false)]

publicclass HelpAttribute : Attribute

{

public HelpAttribute(String Description_in)

   {

this.description = Description_in;

this.verion ="No Version is defined for this class";

   }

protected String description;

public String Description

   {

get

       {

returnthis.description;

       }

   }

protected String version;

public String Version

   {

get

       {

returnthis.version;

       }

//if we  EVE r want our attribute user to set this property,  

//we must specify set method for it

set

       {

this.verion = value;

       }

   }

}

[Help(
"This is Class1")]

publicclass Class1

{

}



[Help(
"This is Class2", Version ="1.0")]

publicclass Class2

{

}



[Help(
"This is Class3", Version ="2.0",  

Description  
="This is do-nothing class")]

publicclass Class3

{

}

當我們在 Class1中查詢Help attribute已經它的屬性,我們將得到:

Help.Description : This is Class1

Help.Version :No Version is defined for this class

因爲我們沒有爲Version這個屬性定義任何任何值,所以在構造函數中設定的值被我們查詢出來了。如果沒有定義任何值,那麼就會賦一個該類型的默認值(例如:如果是int型,默認值就是0)。

現在,查詢Class2的結果是:

Help.Description : This is Class2

Help.Version : 1.0

我們不能爲了可選參數而使用多個構造函數,應該用已命名參數來代替。我們之所以稱它們爲已命名的,是因爲當我們在構造函數爲它們提供值時,我們必須命名它們。例如,在第二個類中,我們如是定義Help

[Help("This is Class2", Version = "1.0")]

AttributeUsage 例子中, 參數”ValidOn”是可選參數,而“Inherited““AllowMultiple“ 是命名參數。

注意:爲了在attribute的構造函數中設定命名參數的值,我們必須爲相應的屬性提供一個set方法否則會引起編譯期錯誤:

'Version' : Named attribute argument can't be a read only property

現在,我們在Class3中查找Help attribute 及其屬性會發生什麼呢?結果是跟上面提到的相同的編譯期錯誤。

'Desciption' : Named attribute argument can't be a read only property

現在我們修改下Help類,爲屬性”Description”加一個set方法。現在的輸出就是:

Help.Description : This is do-nothing class

Help.Version : 2.0

在屏幕後面究竟發生了什麼呢?首先帶有可選參數的構造函數被調用,然後,每個命名參數的set方法被調用,在構造函數中賦給命名參數的值被set方法所覆寫。

參數類型

一個attribute類的參數類型被限定在如下類型中:

  • bool,

  • byte,

  • char,

  • double,

  • float,

  • int,

  • long,

  • short,

  • string

  • System.Type

  • object

  • An enum type, provided that it and any types in which it is nested are publicly accessible. A one-dimensional array involving any of the types listed above

Attributes 標記

假設,我們想把Help attribute 綁定至元素assembly。第一個問題是我們要把Help attribute 放在哪兒才能讓編譯器確定該attribute是綁定至整個assembly呢?考慮另一種情況,我們想把attribute綁定至一個方法的返回類型上,怎樣才能讓編譯器確定我們是把attribute綁定至方法的返回類型上,而不是整個方法呢?

爲了解決諸如此類的含糊問題,我們使用attribute標識符,有了它的幫助,我們就可以確切地申明我們把attribute 綁定至哪一個語言元素。

例如:

[ assembly: Help("this a do-nothing assembly")]

這個在Help attribute 前的assembly標識符確切地告訴編譯器,該attribute被綁定至整個assembly。可能的標識符有:

  • assembly

  • module

  • type

  • method

  • property

  • EVE nt

  • field

  • param

  • return

在運行時查詢Attributes

現在我們明白怎麼創建attribtes和把它們綁定至語言元素。是時候來學習類的使用者該如何在運行時查詢這信息。

爲了查詢一語言元素上綁定的attributes,我們必須使用反射。反射有能力在運行時發現類型信息。

我們可以使用.NET Framework Reflection APIs 通過對整個assembly元數據的迭代,列舉出assembly中所有已定義的類,類型,還有方法。

記住那舊的Helpattribute AnyClass類。




using System;

using System.Reflection;

using System.Diagnostics;



//attaching Help attribute to entire assembly

[assembly : Help(
"This Assembly demonstrates custom attributes

creation and their run
-time query.")]



//our custom attribute class

publicclass HelpAttribute : Attribute

{

public HelpAttribute(String Description_in)

   {

//

// TODO: Add constructor logic here

this.description = Description_in;

//

   }

protected String description;

public String Description

   {

get

       {

returnthis.deescription;



       }            

   }    

}

//attaching Help attribute to our AnyClass

[HelpString(
"This is a do-nothing Class.")]

publicclass AnyClass

{

//attaching Help attribute to our AnyMethod

   [Help(
"This is a do-nothing Method.")]

publicvoid AnyMethod()

   {

   }

//attaching Help attribute to our AnyInt Field

   [Help(
"This is any Integer.")]

publicint AnyInt;

}

class QueryApp

{

publicstaticvoid Main()

   {

   }

}

我們將在接下來的兩節中在我們的 Main方法中加入attribute查詢代碼。

查詢程序集的Attributes

在接下來的代碼中,我們先得到當前的進程名稱,然後用Assembly類中的LoadForm()方法加載程序集,再有用GetCustomAttributes()方法得到被綁定至當前程序集的自定義attributes,接下來用foreach語句遍歷所有attributes並試圖把每個attribute轉型爲Help attribute(即將轉型的對象使用as關鍵字有一個優點,就是當轉型不合法時,我們將不需擔心會拋出異常,代之以空值(null)作爲結果),接下來的一行就是檢查轉型是否有效,及是不是爲空,跟着就顯示Help attribute的“Description”屬性。


class QueryApp


{

publicstaticvoid Main()

   {

       HelpAttribute HelpAttr;


//Querying Assembly Attributes

       String assemblyName;

       Process p  
= Process.GetCurrentProcess();

       assemblyName  
= p.ProcessName +".exe";


       Assembly a  
= Assembly.LoadFrom(assemblyName);


foreach (Attribute attr in a.GetCustomAttributes(true))
       {

           HelpAttr  
= attr as HelpAttribute;

if (null!= HelpAttr)

           {

               Console.WriteLine(
"Description of {0}:\\n{1}",  

                                 assemblyName,HelpAttr.Description);

           }

       }

}

}

程序輸出如下:

Description of QueryAttribute.exe:

This Assembly demonstrates custom attributes creation and

their run-time query.

Press any key to continue

查詢類、方法、類成員的Attributes

下面的代碼中,我們惟一不熟悉的就是Main()方法中的第一行。

Type type = typeof(AnyClass);

它用typeof操作符得到了一個與我們AnyClass類相關聯的Type型對象。剩下的查詢類attributes代碼就與上面的例子是相似的,應該不要解釋了吧(我是這麼想的)。

爲查詢方法和類成員的attributes,首先我們得到所有在類中存在的方法和成員,然後我們查詢與它們相關的所有attributes,這就跟我們查詢類attributes一樣的方式。




class QueryApp

{

publicstaticvoid Main()

   {



       Type type  
=typeof(AnyClass);

       HelpAttribute HelpAttr;





//Querying Class Attributes

foreach (Attribute attr in type.GetCustomAttributes(true))

       {

           HelpAttr  
= attr as HelpAttribute;

if (null!= HelpAttr)

           {

               Console.WriteLine(
"Description of AnyClass:\\n{0}",  

                                 HelpAttr.Description);

           }

       }

//Querying Class-Method Attributes  

foreach(MethodInfo method in type.GetMethods())

       {

foreach (Attribute attr in method.GetCustomAttributes(true))

           {

               HelpAttr  
= attr as HelpAttribute;

if (null!= HelpAttr)

               {

                   Console.WriteLine(
"Description of {0}:\\n{1}",  

                                     method.Name,  

                                     HelpAttr.Description);

               }

           }

       }

//Querying Class-Field (only public) Attributes

foreach(FieldInfo field in type.GetFields())

       {

foreach (Attribute attr in field.GetCustomAttributes(true))

           {

               HelpAttr
= attr as HelpAttribute;

if (null!= HelpAttr)

               {

                   Console.WriteLine(
"Description of {0}:\\n{1}",

                                     field.Name,HelpAttr.Description);

               }

           }

       }

   }

}

The output of the following program is.

Description of AnyClass:

This is a do-nothing Class.

Description of AnyMethod:

This is a do-nothing Method.

Description of AnyInt:

This is any Integer.

Press any key to continue


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