翻譯不是為了翻譯,是為了學習!因為只有翻譯我才能逐句的看完整篇文章。
當然還可以得到各位達人的斧正,讓我由懂得皮毛到漸入佳境!樂哉,幸哉!
原 文(http://www.codeproject.com/Articles/2933/Attributes-in-C)
約定:
1.”attribute” 和 ”attributes” 均不翻譯
2.”property” 譯為“屬性”
3.msdn 中的原句不翻譯
4.”program entity” 譯為 ” 語言元素 ”
Attributes in C#
介紹
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.
public class AnyClass
{
[Obsolete( " Don't use Old method, use New method " , true )]
static void Old( ) { }
static void New( ) { }
public static void 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;
public class HelpAttribute : Attribute
{
}
不管你是否相信我,就這樣我們就已經創建了一個自定義 attribute 。現在就可以用它來裝飾我們的類了,就像我們使用 obsolete attribute 一樣。
[Help()]
public class AnyClass
{
}
注意:按慣例我們是用 ”Attribute“ 作為 attribute 類名的后綴,然而,當我們當我們把 attribute 綁定到某語言元素時,是不包含 “Attribute“ 后綴的。編譯器首先在 System.Attribute 的繼承類中查找該 attribute ,如果沒有找到,編譯器會把 “Attribute“ 追加到該 attribute 的名字后面,然后查找它。
但是迄今為止,該 attribute 沒有任何用處。為了使它有點用處,讓我們在它里面加點東西吧。
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( " this is a do-nothing class " )]
public class 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 ,意思是不能繼承)
讓我們來做點實際的東西吧,我們將把 AttributeUsage attribute 放置在我們的 help attribute 上並在它的幫助下,我們來控制 help attribute 的用法。
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 . 它規定這個 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 " )]public class AnyClass
{
[Help( " this is a do-nothing method " )] // error
public void 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 " )]
public class AnyClass
{
[Help( " this is a do-nothing method " )] // error
public void AnyMethod()
{
}
}
它產生了一個編譯錯誤:
AnyClass.cs: Duplicate 'Help' attribute
Ok !現在我們該討論下最后那個屬性了, ”Inherited”, 指出當把該 attribute 放置於一個基類之上,是否派生類也繼承了該 attribute 。如果綁定至某個 attribute 類的 ”Inherited” 被設為 true, 那么該 attribute 就會被繼承,然而如果綁定至某個 attribute 類的 ”Inherited” 被設為 false 或者沒有定義,那么該 attribute 就不會被繼承。
讓我們假設有如下的類關系。
[Help( " BaseClass " )]
public class Base
{
}
public class 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 " )]
public class Base
{
}
[Help( " DeriveClass " )]
public class Derive : Base
{
}
現在我們查詢相關的 help attribute ,我們將僅僅可以得到派生類的 attribute ,為什么這樣是因為 help attribute 雖然允許被繼承,但不能多次在同一語言元素上使用,所以基類中的 help attribute 被派生類的 help attribute 重寫了。
第四種情況
在第四種情況中,當我們查詢派生類的 help attribute 時,我們可以得到兩個 attributes ,當然是因為 help attribute 既允許被繼承,又允許在同一語言元素上多次使用的結果。
注意: AttributeUsage attribute 僅應用在那種是 System.Attribute 派生的 attriubte 類而且綁定值該 attriubte 類的 AllowMultiple 和 Inherited 均為 false 上才是有效的。
可選參數 vs. 命名參數
可選參數是 attribute 類構造函數的參數。它們是強制的,必須在每次在 attribute 綁定至某語言元素時提供一個值。而另一方面,命名參數倒是真正的可選參數,不是在 attribute 構造函數的參數。
為了更加詳細的解釋,讓我們在 Help 類中添加另外的屬性。
[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;
}
// 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 " )]
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 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 中所有已定義的類,類型,還有方法。
記住那舊的 Help attribute 和 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)
{
//
// TODO: Add constructor logic here
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()
{
}
}
我們將在接下來的兩節中在我們的 Main 方法中加入 attribute 查詢代碼。
查詢程序集的 Attributes
在接下來的代碼中,我們先得到當前的進程名稱,然后用 Assembly 類中的 LoadForm ()方法加載程序集,再有用 GetCustomAttributes ()方法得到被綁定至當前程序集的自定義 attributes ,接下來用 foreach 語句遍歷所有 attributes 並試圖把每個 attribute 轉型為 Help attribute (即將轉型的對象使用 as 關鍵字有一個優點,就是當轉型不合法時,我們將不需擔心會拋出異常,代之以空值( null )作為結果),接下來的一行就是檢查轉型是否有效,及是不是為空,跟着就顯示 Help attribute 的“ Description ”屬性。
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);
}
}
}
}
程序輸出如下:
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
{
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);
}
}
}
}
}
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