|
原文出處 Attributes in C#, CodeProject using System;
public class AnyClass
{
[Obsolete("別用Old這個(gè)老方法了,請(qǐng)用New方法", true)]
static void Old( ) { }
static void New( ) { }
public static void Main( )
{
Old( );
}
}
在這個(gè)例子里我們使用了Obsolete(“陳舊的”)屬性,它會(huì)將其所修飾的程序?qū)嶓w(類、方法、數(shù)據(jù)成員等)說(shuō)明為已廢棄不用的。第一個(gè)參數(shù)—一個(gè)字符串說(shuō)明這個(gè)實(shí)體為何被廢棄、由誰(shuí)代替。實(shí)際上這個(gè)字符串的內(nèi)容你想寫什么都可以。第二個(gè)參數(shù)則告訴編譯器將用戶對(duì)此實(shí)體的調(diào)用視作一個(gè)編譯錯(cuò)誤。這個(gè)參數(shù)的缺省值為false,表示編譯器僅將用戶對(duì)其的調(diào)用視作警告。編譯上面這段代碼時(shí),我們將會(huì)得到一個(gè)編譯錯(cuò)誤(譯注:注意編譯錯(cuò)誤后附的提示了嗎?):
AnyClass.Old() is obsolete:“別用Old這個(gè)老方法了,請(qǐng)用New方法” 開發(fā)自定義的屬性現(xiàn)在開始開發(fā)我們自己的屬性吧。這兒有一個(gè)小竅門:從C#定義的System.Attribute類派生我們的屬性類(從抽象基類System.Attribute直接或間接地派生一個(gè)類,該派生類都是一個(gè)屬性類。一個(gè)屬性類的聲明就定義了一種新的屬性類型),然后得到了這樣一個(gè)聲明: using System;
public class HelpAttribute : Attribute
{
}
不管你相不相信,我們已經(jīng)創(chuàng)建了一個(gè)自定義的屬性。我們可以象這樣用它修飾任何的類:
[Help()]
public class AnyClass
{
}
注意:在屬性類名與后綴Attribute間存在一個(gè)自動(dòng)的編譯轉(zhuǎn)換。因此當(dāng)我們用一個(gè)屬性去修飾一個(gè)程序?qū)嶓w時(shí),不需要給出Attribute這個(gè)后綴。編譯器首先會(huì)在System.Attribute的所有派生類中進(jìn)行匹配,如果沒(méi)有找到匹配屬性,它就將屬性名加上Attribute后綴名后再進(jìn)行匹配?! ?br> 目前我們的這個(gè)屬性還沒(méi)什么用,讓我們加點(diǎn)內(nèi)容吧。在這個(gè)示例里,我們?yōu)樽远x的屬性類添加了一個(gè)數(shù)據(jù)屬性Description(Property),我們將在本文的最后演示如何在運(yùn)行時(shí)查詢這些信息。 using System;
public class HelpAttribute : Attribute
{
public HelpAttribute(String Descrition_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}
[Help("這是個(gè)什么也不做的類")]
public class AnyClass
{
}
定義/控制自定義屬性的使用AttributeUsage 類是另一個(gè)預(yù)定義的屬性類,以幫助我們控制自定義屬性的使用。亦即我們可以定義自定義屬性類的屬性。這個(gè)類描述了如何使用自定義的屬性類。AttributeUsage有三個(gè)數(shù)據(jù)屬性可用以修飾我們的自定義的屬性:
讓我們做點(diǎn)具體的吧。我們將會(huì)用一個(gè)AttributeUsage屬性修飾我們的屬性類,以控制其作用范圍: using System;
[AttributeUsage(AttributeTargets.Class), AllowMultiple = false, Inherited = false ]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}
先看看AttributeTargets.Class,說(shuō)明了我們的Help屬性只能用以修飾類,下面的這段代碼將會(huì)導(dǎo)致一個(gè)編譯錯(cuò)誤(“屬性Help不能用在這樣的聲明上,它只能用在類的聲明上”),因?yàn)槲覀冇肏elp屬性去修飾方法AnyMethod()了: [Help("this is a do-nothing class")]
public class AnyClass
{
[Help("this is a do-nothing method")] //error
public void AnyMethod()
{
}
}
編譯錯(cuò)誤:
AnyClass.cs: Attribute ''Help'' is not valid on this declaration type. It is valid on ''class'' declarations only.當(dāng)然我們可以AttributeTargets.All來(lái)允許Help屬性修飾任何類型的程序?qū)嶓w。AttributeTargets可能的值包括:
[Help("this is a do-nothing class")]
[Help("it contains a do-nothing method")]
public class AnyClass
{
[Help("this is a do-nothing method")] //這也是錯(cuò)誤的,因?yàn)镠elp屬性只能修飾類
public void AnyMethod()
{
}
}
編譯錯(cuò)誤:
AnyClass.cs: Duplicate ''Help'' attribute我們?cè)賮?lái)談?wù)凙ttributeUsage的最后一個(gè)數(shù)據(jù)屬性Inherited:定義了自定義屬性的修飾是否可由被修飾類的派生類繼承?;谙率敬a表示的繼承關(guān)系,讓我們看看會(huì)發(fā)生什么吧: [Help("BaseClass")]
public class Base
{
}
public class Derive : Base
{
}
我們選擇了AttributeUsage的四種組合:
AttributeUsage只能用于System.Attribute的派生類,且該派生類的AllowMultiple與Inherited都為false。 定位參數(shù)與命名參數(shù) 定位參數(shù)是屬性類構(gòu)造子(Constructor)的參數(shù)。它們是每次使用該屬性修飾某個(gè)程序?qū)嶓w時(shí)都必須提供值的參數(shù)。相對(duì)的,命名參數(shù)則是可選參數(shù),它也不是屬性類構(gòu)造子的參數(shù)。為了詳細(xì)解釋它們的含義,讓我們給Help屬性類加點(diǎn)內(nèi)容,然后看看下面的示例: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class 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
{
return this.description;
}
}
protected String version;
public String Version
{
get
{
return this.version;
}
//即便我們不想我們的屬性用戶設(shè)置Version這個(gè)數(shù)據(jù)屬性,我們也不得不提供set方法
set
{
this.verion = value;
}
}
}
[Help("This is Class1")]
public class Class1
{
}
[Help("This is Class2", Version = "1.0")]
public class Class2
{
}
[Help("This is Class3", Version = "2.0",
Description = "This is do-nothing class")]
public class Class3
{
}
我們查詢Class1的Help屬性信息時(shí),會(huì)得到下列結(jié)果:
Help.Description : This is Class1 Help.Version :No Version is defined for this class如果我們不定義Version數(shù)據(jù)屬性的值,那么構(gòu)造子函數(shù)體內(nèi)所賦的缺省值將會(huì)使用。如果也沒(méi)有,那么該數(shù)據(jù)屬性對(duì)應(yīng)的數(shù)據(jù)類型的缺省值將被使用(比如int類型的缺省值為0)。查詢Class2將會(huì)得到這樣的結(jié)果: Help.Description : This is Class2 Help.Version : 1.0請(qǐng)不要為了可選的參數(shù)而提供多個(gè)構(gòu)造子的重載版本,而應(yīng)該將它們定義成命名參數(shù)。我們之所以稱其為“命名的”,是我們?yōu)榱四茉跇?gòu)造子里給它們賦值,不得不用一個(gè)個(gè)的標(biāo)識(shí)符定義和訪問(wèn)它們。比如在第二個(gè)類中Help屬性的使用[Help("This is Class2", Version = "1.0")] 你瞧,AttributeUsage的ValidOn參數(shù)就是一個(gè)定位參數(shù),而Inherited與AllowMultiple則是命名參數(shù)?! ∽⒁猓涸趯傩灶惖臉?gòu)造子中給命名參數(shù)賦值,我們必須為它提供一個(gè)相應(yīng)的set方法,否則會(huì)導(dǎo)致這樣的編譯錯(cuò)誤(Version不能是一個(gè)只讀的數(shù)據(jù)屬性): ''Version'' : Named attribute argument can''t be a read only property當(dāng)我們查詢Class3的Help屬性信息時(shí)會(huì)發(fā)生什么呢?會(huì)這樣-因上述的原因?qū)е戮幾g錯(cuò)誤(Description不能是只讀的數(shù)據(jù)屬性): ''Desciption'' : Named attribute argument can''t be a read only property所以還是給Description添加一個(gè)set方法吧。這樣會(huì)得到正確的輸出結(jié)果: This is do-nothing class Help.Version : 2.0這是因?yàn)闃?gòu)造子利用定位參數(shù)構(gòu)造一個(gè)屬性時(shí),它會(huì)調(diào)用所有命名參數(shù)的set方法。構(gòu)造子里的賦值行為實(shí)際均由各命名參數(shù)對(duì)應(yīng)的數(shù)據(jù)屬性的set方法完成,被其覆寫(Override)了。 參數(shù)類型一個(gè)屬性類的參數(shù)可使用的數(shù)據(jù)類型限于:
屬性標(biāo)識(shí) 讓我們想象一下,怎么才能把我們的Help屬性用到一個(gè)完整的程序集(assembly)上?首先要面對(duì)的問(wèn)題是該把Help屬性放在哪兒,以便讓編譯器識(shí)別出它是屬于一個(gè)程序集的?再考慮另一種情況:我們想把一個(gè)屬性用在某個(gè)方法的返回類型上時(shí),編譯器如何才能確定我們把它用在了返回類型而不是這個(gè)方法本身之上?要解決這么多含糊的問(wèn)題,我們需要屬性標(biāo)識(shí)。借助屬性標(biāo)識(shí)的幫助,我們可以明確地告訴程序集我們希望把屬性放在哪兒。比如: [assembly: Help("this a do-nothing assembly")]
這個(gè)Help屬性前的assembly標(biāo)識(shí)符顯式地告訴了編譯器,當(dāng)前這個(gè)Help屬性用于整個(gè)程序集??捎玫臉?biāo)識(shí)符包括:
在運(yùn)行時(shí)查詢屬性 我們已經(jīng)知道了如何創(chuàng)建屬性并如何在程序中使用它們。現(xiàn)在該學(xué)習(xí)我們所建屬性類的用戶如何才能在運(yùn)行時(shí)查詢?cè)搶傩灶惖男畔⒘?。要查詢一個(gè)程序?qū)嶓w的所有屬性信息,我們得使用反射(reflection)-在運(yùn)行時(shí)發(fā)現(xiàn)類型信息的一種功能。 我們可以直接使用.NET Framework提供的反射Reflection API來(lái)枚舉一個(gè)完整程序集的所有元數(shù)據(jù)(metadata),并產(chǎn)生該程序集所有類、類型、方法的列表。還記得之前的Help屬性和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
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.deescription;
}
}
}
//attaching Help attribute to our AnyClass
[HelpString("This is a do-nothing Class.")]
public class AnyClass
{
//attaching Help attribute to our AnyMethod
[Help("This is a do-nothing Method.")]
public void AnyMethod()
{
}
//attaching Help attribute to our AnyInt Field
[Help("This is any Integer.")]
public int AnyInt;
}
class QueryApp
{
public static void Main()
{
}
}
我們將在接下來(lái)的兩節(jié)里在我們的Main方法里加入屬性查詢的代碼。 查詢程序集的屬性 在接下來(lái)的代碼片段里,我們獲取當(dāng)前進(jìn)程的名字,并使用Assembly類的LoadFrom方法裝載程序集。然后我們使用GetCustomAttributes方法獲取當(dāng)前程序集的所有自定義屬性。接下來(lái)的foreach語(yǔ)句又遍歷所有的屬性對(duì)象,并試著將這些屬性轉(zhuǎn)化為Help屬性(使用as關(guān)鍵字進(jìn)行轉(zhuǎn)換,如果轉(zhuǎn)換失敗,將會(huì)返回一個(gè)空值而不是觸發(fā)一個(gè)異常)。再后的一條語(yǔ)句是指如果轉(zhuǎn)換成功,則顯示Help屬性的所有數(shù)據(jù)屬性信息。 class QueryApp
{
public static void 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);
}
}
}
}
程序的輸出結(jié)果:
Description of QueryAttribute.exe: This Assembly demonstrates custom attributes creation and their run-time query. Press any key to continue 查詢類、方法和域的屬性在下面的代碼片段里,和上面的代碼不同的只是Main方法的第一條語(yǔ)句變成了: Type type = typeof(AnyClass);它使用typeof操作符返回AnyClass對(duì)應(yīng)的Type對(duì)象。其余的代碼也類似上述的代碼,我想不需要再做解釋了吧。要查詢方法和域的屬性,我們首先要獲得當(dāng)前類中所有方法和類,然后再用類似于查詢類的屬性的方法來(lái)查詢與之對(duì)應(yīng)的屬性。 class QueryApp
{
public static void 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);
}
}
}
}
}
下面是程序輸出:
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 關(guān)于作者Sadaf Alvi,卡拉奇大學(xué)的自然科學(xué)學(xué)士,主頁(yè)http://www24./salvee |
|
|