![[CLR via C#]18. Attribute [CLR via C#]18. Attribute](https://image.shishitao.com:8440/aHR0cHM6Ly9ia3FzaW1nLmlrYWZhbi5jb20vdXBsb2FkL2NoYXRncHQtcy5wbmc%2FIQ%3D%3D.png?!?w=700&webp=1)
[assembly: MyAttr()] // 应用于程序集
[module: MyAttr()] // 应用于模块 [type: MyAttr()] // 应用于类型
internal sealed class SomeType<[typevar: MyAttr()] T> { // 应用于泛型类型变量 [field: MyAttr()] // 应用于字段
public Int32 SomeField = ; [return: MyAttr()] // 应用于返回值
[method: MyAttr()] // 应用于方法
public Int32 SomeMethod(
[param: MyAttr()] // 应用于方法参数
Int32 SomeParam) { return SomeParam; } [property: MyAttr()] // 应用于属性
public String SomeProp {
[method: MyAttr()] // 应用于get访问器方法
get { return null; }
} [event: MyAttr()] // 应用于事件
[field: MyAttr()] // 应用于编译器生成的字段
[method: MyAttr()] // 应用于编译器生成的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 == ? "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类的定义元数据和实现存在于程序集中。