[assembly: MyAttr(1)] // 应用于程序集 [module: MyAttr(2)] // 应用于模块 [type: MyAttr(3)] // 应用于类型 internal sealed class SomeType<[typevar: MyAttr(4)] T> { // 应用于泛型类型变量 [field: MyAttr(5)] // 应用于字段 public Int32 SomeField = 0; [return: MyAttr(6)] // 应用于返回值 [method: MyAttr(7)] // 应用于方法 public Int32 SomeMethod( [param: MyAttr(8)] // 应用于方法参数 Int32 SomeParam) { return SomeParam; } [property: MyAttr(9)] // 应用于属性 public String SomeProp { [method: MyAttr(10)] // 应用于get访问器方法 get { return null; } } [event: MyAttr(11)] // 应用于事件 [field: MyAttr(12)] // 应用于编译器生成的字段 [method: MyAttr(13)] // 应用于编译器生成的add&&remove方法 public event EventHandler SomeEvent; } [AttributeUsage(AttributeTargets.All)] public class MyAttr : Attribute { public MyAttr(Int32 x) { } }
前面介绍了如何应用一个attribute,接下来看看attribute到底是什么?
attribute实际是一个类的实例。为了符合"公共语言规范"(CLS)的要求,attribute类必须直接或间接地从公共抽象类System.Attribute派生。C#只允许使用符合CLS规范的attribute。attribute类是可以在任何命名空间中定义的。
[DllImport("kernel32",CharSet = CharSet.Auto, SetLastError = true)]
这一行代码语法表面上很奇怪,因为调用构造器时永远不会出现这样的语法。查阅DllImportAttbute类的文档,会发现它只接受一个String类型的参数。在这个例子中,"Kernel32"这个String类型的参数已经传给它了。构造器的参数称为"定位参数",而且是强制性的。也就是说,应用attribute时,必须指定参数。
public enum Color { Red } [AttributeUsage(AttributeTargets.All)] internal sealed class SomeAttribute : Attribute { public SomeAttribute(String name, Object o, Type[] types) { // 'name' 引用了一个String类型 // 'o' 引用了一个合法类型(如有必要,就进行装箱) // 'types' 引用一个一维0基Type数组 } } [Some("Jeff", Color.Red, new Type[] { typeof(Math), typeof(Console) })] internal sealed class SomeType { }
逻辑上,当编译器检测到一个目标元素应用了一个attribute时,编译器会调用attribute类的构造器,向它传递任何指定的参数,从而构造attribute类的一个实例。然后,编译器会采用增强型构造器语法所指定的值,对任何公共字段和属性进行初始化。在构造并初始化好定制attribute类的对象之后,编译器会将这个attribute对象序列化到目标元素的元数据表记录项中。
public static String Format(Type enumType, Object value, String format) { // 枚举类型是否应用了FlagsAttrobute类型的一个实例 if(enumType.IsDefined(typeof(FlagsAttribute),false)){ //如果是,就执行代码,将值视为一个位标志枚举类型 ... }else { // 如果不是,就执行代码,将值视为一个普通枚举类型 } }
上述代码调用Type的IsDefined方法,要求系统查看枚举类型的元数据,检查是否关联了FlagsAttribute类的一个实例。如果IsDefined返回true,表面FlagsAttribute的一个实例已于枚举类型关联,Format方法会认为值包含了一个位标志集合。如果IsDefined放回false,Format方法会认为值是一个普通的枚举类型。
方法名称 | 说明 |
IsDefined | 如果至少有一个指定的Attribute派生类的实例如目标关联,就放回true。这个方法效率很高,因为它不构造(反序列化)attribute类的任何实例 |
GetCustomAttribute |
返回引用于目标的指定attribute类的一个实例。实例使用编译时指定的参数、字段和属性来构造。如果目标没有引用任何attribute类的实例,就返回null。如果目标应用了指定attribute的多个实例,就抛出异常。方法通常用于已将AllowMultiple设为false的attribute。
|
GetCustomAttributes | 返回一个数组,其中每个元素都是应用于目标的指定attribute类的一个实例。如果不为这个方法指定具体的attribute类,数组中包含的就是已应用的所有attribute的实例,不管它们是什么类。每个实例都使用编译时指定的参数、字段和属性来构造(反序列化)。如果目标没有应用任何attribute类的实例,就返回一个空数组。该方法通常用于已将AllowMultiple设为true的attribute,或者用于列出已应用的所有attribute。 |
[assembly: CLSCompliant(true)] [Serializable] [DefaultMemberAttribute("Main")] [DebuggerDisplayAttribute("Richter", Name = "Jeff", Target = typeof(Program))] public sealed class Program { [Conditional("Debug")] [Conditional("Release")] public void DoSomething() { } public Program() { } [assembly: CLSCompliant(true)] [STAThread] public static void Main() { // 显示应用于这个类型的attribute集 ShowAttributes(typeof(Program)); // 获取与类型关联的方法集 MemberInfo[] members = typeof(Program).FindMembers( MemberTypes.Constructor | MemberTypes.Method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static, Type.FilterName, "*"); foreach (MemberInfo member in members) { // 显示应用于这个成员的attribute集 ShowAttributes(member); } Console.Read(); } private static void ShowAttributes(MemberInfo attributeTarget) { Attribute[] attributes = Attribute.GetCustomAttributes(attributeTarget); Console.WriteLine("Attributes applied to {0}: {1}", attributeTarget.Name, (attributes.Length == 0 ? "None" : String.Empty)); foreach (Attribute attribute in attributes) { // 显示已应用的每个attribute的类型 Console.WriteLine(" {0}", attribute.GetType().ToString()); if (attribute is DefaultMemberAttribute) Console.WriteLine(" MemberName={0}", ((DefaultMemberAttribute)attribute).MemberName); if (attribute is ConditionalAttribute) Console.WriteLine(" ConditionString={0}", ((ConditionalAttribute)attribute).ConditionString); if (attribute is CLSCompliantAttribute) Console.WriteLine(" IsCompliant={0}", ((CLSCompliantAttribute)attribute).IsCompliant); DebuggerDisplayAttribute dda = attribute as DebuggerDisplayAttribute; if (dda != null) { Console.WriteLine(" Value={0}, Name={1}, Target={2}", dda.Value, dda.Name, dda.Target); } } Console.WriteLine(); } }
输出结果为:
[Flags] internal enum Accounts { Savings = 0x0001, Checking = 0x0002, Brokerage = 0x0004 } [AttributeUsage(AttributeTargets.Class)] internal sealed class AccountsAttribute : Attribute { private Accounts m_accounts; public AccountsAttribute(Accounts accounts) { m_accounts = accounts; } public override Boolean Match(Object obj) { // 如果基类实现了Match,而且基类 // 不是Attribute,就取消对下面这行的注释 //if (!base.Match(obj)) //{ // return false; //} if (obj == null) { return false; } if (this.GetType() != obj.GetType()) { return false; } AccountsAttribute other = (AccountsAttribute)obj; // 比较字段,判断它们是否有相同的值 if ((other.m_accounts & m_accounts) != m_accounts) { return false; } return true; // 对象匹配 } public override Boolean Equals(Object obj) { //如果基类实现了Equals,而且基类不能Object //就取消对下面的注释 //if (!base.Equals(obj)) //{ // return false; //} if (obj == null) { return false; } if (this.GetType() != obj.GetType()) { return false; } AccountsAttribute other = (AccountsAttribute)obj; // 比较字段,判断它们是否有相同的值 if (other.m_accounts != m_accounts) { return false; } return true; // Objects are equal } // 还需要重写GetHashCode,因为我们重写了Equals public override Int32 GetHashCode() { return (Int32)m_accounts; } } [Accounts(Accounts.Savings)] internal sealed class ChildAccount { } [Accounts(Accounts.Savings | Accounts.Checking | Accounts.Brokerage)] internal sealed class AdultAccount { } public class Program { public static void Main() { CanWriteCheck(new ChildAccount()); CanWriteCheck(new AdultAccount()); // 只是为了演示在一个没有应用AccountsAttribute的方法也能正确工作 CanWriteCheck(new Program()); Console.Read(); } private static void CanWriteCheck(Object obj) { // 构造attribute类型的一个实例,并把它初始化我们要 // 显示查找的内容 Attribute checking = new AccountsAttribute(Accounts.Checking); // 构造应用于类型的attribute实例 Attribute validAccounts = Attribute.GetCustomAttribute( obj.GetType(), typeof(AccountsAttribute), false); // 如果attribute应用于类型,而且attribute指定了 // "Checking" 账户, 表面该类可以开支票 if ((validAccounts != null) && checking.Match(validAccounts)) { Console.WriteLine("{0} types can write checks.", obj.GetType()); } else { Console.WriteLine("{0} types can NOT write checks.", obj.GetType()); } } }
输出结果:
//#define TEST #define VERIFY namespace ConsoleTest { using System; using System.Diagnostics; [Conditional("TEST")] [Conditional("VERIFY")] public sealed class CondAttribute : Attribute { } [Cond] public class Program { public static void Main() { Console.WriteLine("CondAttribute is {0} applied to Program type.", Attribute.IsDefined(typeof (Program), typeof (CondAttribute)) ? "" : "not"); Console.Read(); } } }
编译器如果发现向目标元素应用了CondAttribute的一个实例,那么当含有目标元素的代码编译时,只有定义TEST或VERIFY符号的前提下,编译器才会在元数据中生成attribute信息。虽然如此,attribute类的定义元数据和实现存在于程序集中。