自定義屬性,使用聲明式編程的方式,HTML也是屬於這種編程方式。
17.1 使用自定義屬性
只是將一些附加信息與某個目標元素關聯起來。編譯器在托管模塊的元數據中生成額外的信息。
從System.Attribute派生,所有符合CLS的屬性都是從這個基類派生。
有定位參數和命名參數兩種,前者必須指定。
可以將多個屬性應用於單個目標元素,用逗號分割。
17.2 定義自己的屬性
屬性類標准寫法: [AttributeUsage(AttributeTargets.Enum, Inherited = true, AllowMultiple = false)]
public class FlagAttribute : System.Attribute { public FlagAttribute() { } }
注意:1.屬性就是類的一個實例,因此屬性類至少要有一個公共構造器。如果class沒有ctor,就生成 默認ctor,所以也可以編譯通過。
2.這個類不要提供任何公共方法/事件
3.FlagAttribute使用的時候,可以簡寫為[Flag]
4.AttributeTarget枚舉,限定屬性的應用范圍,上面程序說明Flag只能用於Enum類型; AttributeTarget.All表示適用於所有類型。
5.AllowMultiple指出是否可以將屬性多次應用於單個目標:
大部分屬性只能使用一次,如以下代碼會編譯出錯,因為沒有任何意義:
[Flag] [Flag] public enum Color { Red }
少數屬性有必要將屬性多次應用於單個目標,如Conditional屬性類(見17.7)
6.Inherited指出屬性是否能由派生類和重寫成員繼承,如下代碼:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)] internal class TastyAttribute : System.Attribute { public TastyAttribute() { } } [Tasty][Serializable] internal class BaseType { [Tasty] protected virtual void DoSomething() { } } internal class DeriveType : BaseType { protected override void DoSomething() { } }
這裡,因為繼承的關系,DerivedType及其方法都有屬性[Tasty]。由於Serializable屬性被標記為不 可繼承,所以DerivedType不可以序列化。
只有class/method/properties/field/event/方法返回值/方法參數,是可繼承的,inherited設為 true。
Inherited屬性不會為派生類生成額外的元數據,不影響派生類行為,只是在程序集中生成額外的元數 據。
補充:從AttributeUsage類的FCL源碼,可以看出:
不設置AttributeUsage屬性,默認為 [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
17.3 屬性的ctor/Field/Property的數據類型,不能是靜態的
必須限制在盡量小的類型范圍內。
盡量應該避免使用,因為會在ctor中傳遞數組參數,不兼容於CLS(非0基數組不符合CLS)
在屬性中定義Type類型,要使用typeof()方法傳遞參數;定義Object類型,可以傳遞Int32/String等 常量表達式(包括null)如果常量表達式為值類型,則執行時需要裝箱。
public enum Color { Red } class SomeAttribute : Attribute { public SomeAttribute(String name, Object o, Type[] type) { } } [Some("Jeff", Color.Red, new Type[]{typeof(Math), typeof(Console)})] class SomeType { }
17.4 檢測自定義屬性的使用
在枚舉中,介紹了Format靜態方法,功能基本同ToString()方法,但允許value傳遞一個數值,而不僅 僅是一個Enum類型
Enum.Format(typeof(Color), 3, "G");
這個方法的實現如下:
public static String Format(Type enumType, Object value, String format) { //檢查枚舉類型是否應用了Flag屬性類型的一個實例 //false表示不從其派生類中繼續查找 if(enumType.IsDefined(typeof(FlagsAttribute), false)) { //如果是,將value作為一個位標志處理 } else { //如果不是,將value作為一個普通枚舉類型 } }
以上使用了Type的IsDefined方法,檢查一個類型上的屬性。
以下介紹檢查一個目標的屬性:如Assembly,module,方法,有3個方法可以使用:
1.IsDefined方法,只是檢查,不構造屬性類的實例,效率很高
Attribute.IsDefine一般有兩個參數,第一個是要檢查的目標.GetType(),第二個是typeof(屬性)。 當目標是Attribute/Type/MemthodInfo時,要使用第三個參數,決定是否要從派生類查找。
2.GetCustomAttributes方法,返回一個應用於目標的屬性數組
public static void ShowAttribute(MemberInfo attributeTarget) { Attribute[] attributes = Attribute.GetCustomAttributes (attributeTarget); foreach (Attribute attribute in attributes) { //遍歷屬性數組 } }
3.GetCustomAttribute方法,返回應用於目標的制定屬性類的一個實例,使用方法見下一節。
17.5 兩個屬性實例的相互匹配
自定義屬性,要重寫Match()方法,才可以比較兩個屬性實例,否則,會調用System.Attribute的 match()方法,而後者,只是調用Mquals方法。
實例展示了Match的重寫,以及上一節GetCustomAttribute方法的使用
[Accounts(Accounts.Savings)] class ChildAccount { } [Accounts(Accounts.Savings | Accounts.Checking)] class AdultAccount { } class Program { static void Main(string[] args) { CanWriteCheck(new ChildAccount()); CanWriteCheck(new AdultAccount()); } private static void CanWriteCheck(Object obj) { Attribute checking = new AccountsAttribute(Accounts.Checking); //以下語句展示了Attribute的GetCustomAttribute方法 Attribute validAccount = Attribute.GetCustomAttribute(obj.GetType(), typeof(AccountsAttribute), false); if ((validAccount != null) && checking.Match(validAccount)) { //obj有寫的權限 } } }
[Flags]
enum Accounts { Savings = 0x001, Checking = 0x002, } [AttributeUsage(AttributeTargets.Class)] public class AccountsAttribute : Attribute { private Accounts m_accounts; public AccountsAttribute(Accounts accounts) { m_accounts = accounts; } public override bool Match(object obj) { //如果基類實現了Match,而基類又不是System.Attribute,就取消下面這段注 釋 //if (!base.Match(obj)) //{ // return false; //} //如果基類實現Match,則下面這條語句可以刪除 //因為this肯定不為null,如果obj為null,則肯定不匹配 if (obj == null) { return false; } //如果基類實現Match,則下面這條語句可以刪除 //對象屬於不同類型,肯定不匹配 if (this.GetType() != obj.GetType()) { return false; } //轉型一定成功,因為由上條語句,對象肯定具有相同的類型 AccountsClass other = (AccountsClass)obj; //以下語句要分別比較各個字段,其中有一個不對就返回false,舉一個例子: if ((other.m_accounts & m_accounts) != m_accounts) { return false; } return true; } }
17.6 查找自定義屬性,同時不創建屬性類(即不執行屬性類的代碼)
使用System.Reflection.CustomAttributeData類,使用其靜態方法GetCustomAttributes(),獲取一 個與目標關聯的屬性。4個重載版本,分別接受Assembly/Module/ParameterInfo/MemberInfo參數。
同時要配合使用Assembly.ReflectionOnlyLoad()方法,得到程序集,然後再使用 GetCustomAttributes()方法進行分析
CustomAttributeData類的3個只讀屬性:
1.Constructor,返回ctor形式:Void .ctor(String.String) //這裡表示ctor有一個String參 數
2.ConstructorArguments,泛型,要傳遞給ctor的參數
3.NamedArguments,泛型,返回要設置的字段,不在ctor中設置的
public static void ShowAttribute(MemberInfo attributeTarget) { IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(attributeTarget); foreach (CustomAttributeData attribute in attributes) { //遍歷屬性數組 } } }
17.7 條件屬性類:使用了System.Diagnostics.ConditionalAttribute的屬性類
#define VERIFY using System.Diagnostics; [Conditional("TEST")] [Conditional("VERIFY")] class CondAttribute : Attribute { } [Cond] public class Program { static void Main() { Console.WriteLine(Attribute.IsDefined(typeof(Program), typeof (CondAttribute))); } }
這裡,#define VREIFY語句要定義在using之前,這條語句的有無,決定了CondAttribute是否會在IL 中生成。