自定義屬性,使用聲明式編程的方式,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 中生成。