c#中的特性本质上都是一个类。所有我们自定义的特性都派生于Attribute基类。
仅仅定义特性类,并应用自己想要的所有势力。这样做除了在程序集中生成额外的元数据,没有其他的任何意义。应用程序代码的行为不会有任何的改变。
FCL提供了很多种方式来检测特性的存在。为了简化讨论,让我们聚焦于System.Reflection.CustomAttributeExtensions类定义的扩展方法。该类定义了三个静态方法来获取与目标关联的特性: IsDefined, GetCustomAttributes和GetCustomAttribute。每个方法都有几个重载版本。例如:每个方法都有一个版本能操作类型成员(类、结构、枚举、接口、委托、构造器、方法、属性、字段、事件和返回类型)。这些方法在元数据上反射以查找与CLS相容的定制特性类型的实例。
只判断一个特性是否存在
如果只是想判断目标是否应用了一个特性,那么应该调用IsDefined,因为它比另两个方法更高效。但是我们知道,将特性应用于目标时,可以为特性构造器指定参数,并可选择设置字段和属性。使用IsDefined不会构造特性对象,不会调用构造器,也不会设置子段和属性。
//类型是否应用了AccountsAttribute类型的实例
public void IsAttribute()
{
if (this.GetType().IsDefined(typeof(AccountsAttribute), false))
{
//如果是,执行的代码
}
else
{
//如果不是,执行的代码
}
}
两个特性实例的相互匹配
通过构造attribute类型的一个实例,并把它初始化成我们要显示查找的内容。将构造的类型和应用的特性类型进行比较,来判定是否应用一个特性类型。
System.Attribute重写了Object的Equals方法,该方法会在内部比较两个对象的类型。不一致会返回false。如果一致,Equals会利用反射来比较两个特性对象中的字段值(为每个字段都调用Equals)。所有字段都匹配就返回true,否者返回false。可在自己的定制特性类中重写Equals来移除反射的使用,从而提升性能。
System.Attribute还公开了虚方法Match,可重写它来提供更丰富的语义。Match的默认实现只是调用Equal方法并返回他的错误结构。
[Flags]
public enum Accounts
{
Savings = 0x0001,
Checking = 0x0002,
Brokerage = 0x0004
}
[AttributeUsage(AttributeTargets.Class)]
public class AccountsAttribute: Attribute
{
private Accounts _accounts;
public AccountsAttribute(Accounts accounts)
{
_accounts = accounts;
}
public override bool Match(object obj)
{
if (obj == null)
{
return false;
}
if (this.GetType() != obj.GetType())
{
return false;
}
AccountsAttribute other = (AccountsAttribute)obj;
if ((other._accounts & _accounts) != _accounts)
{
return false;
}
return true;
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (this.GetType() != obj.GetType())
{
return false;
}
AccountsAttribute other = (AccountsAttribute)obj;
if (other._accounts != _accounts)
{
return false;
}
return true;
}
}
[Accounts(Accounts.Savings)]
internal sealed class ChildAccount
{
}
class Program
{
static void Main(string[] args)
{
CanWriteCheck(new ChildAccount());
}
private static void CanWriteCheck(Object obj)
{
//构造attribute类型的一个实例,并把它初始化成
//我们要显示查找的内容
Attribute checking = new AccountsAttribute(Accounts.Checking);
//构造应用于类型的特性实例
Attribute validAccounts = Attribute.GetCustomAttribute(obj.GetType(), typeof(AccountsAttribute), false);
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());
}
}
}
检测定制特性时不创建从Attribute派生的对象
在某些安全性要求严格的场合,这个技术能保证不执行从Attribute派生的类中的代码。毕竟,调用Attribute的GetCustomAttribute或者GetCustomAttributes方法时,这些方法会在内部调用特性类的构造器,而且可能调用属性的set访问器方法。
可用 System.Reflection.Custom.AttributeData 类在查找特性的同时禁止执行特性类中的代码。该类定义了静态方法GetCustomAttributes来获取与目标关联的特性。方法有四个重载版本,分别获取一个 Assembly, Module, ParameterInfo 和 MemberInfo。
class Program
{
static void Main(string[] args)
{
//显示应用于这个类型的特性集
ShowAttributes(typeof(AccountsAttribute));
//获取与类型关联的方法集
var members = from m in typeof(AccountsAttribute).GetTypeInfo().DeclaredMembers.OfType<MethodBase>()
where m.IsPublic
select m;
foreach (MemberInfo member in members)
{
//显示应用于这个成员的特性集
ShowAttributes(member);
}
Console.ReadKey();
}
private static void ShowAttributes(MemberInfo attributeTarget)
{
IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(attributeTarget);
Console.WriteLine("Attributes applied to {0}, {1}", attributeTarget.Name, (attributes.Count == 0 ? "None" : String.Empty));
foreach (CustomAttributeData attribute in attributes)
{
//显示所应用的每个特性的类型
Type t = attribute.Constructor.DeclaringType;
Console.WriteLine(" {0}", t.ToString());
Console.WriteLine("Constructor called={0}", attribute.Constructor);
IList<CustomAttributeTypedArgument> posArgs = attribute.ConstructorArguments;
Console.WriteLine(" Positional arguments passed to constructor:" + ((posArgs.Count == 0) ? " None" : String.Empty));
foreach (CustomAttributeTypedArgument pa in posArgs)
{
Console.WriteLine(" Type={0}, Value={1}", pa.ArgumentType, pa.Value);
}
IList<CustomAttributeNamedArgument> namedArgs = attribute.NamedArguments;
Console.WriteLine(" Named arguments set after construction:" + (namedArgs.Count == 0 ? "None" : String.Empty));
foreach (CustomAttributeNamedArgument na in namedArgs)
{
Console.WriteLine(" Name={0}, Type={1}, Value={2}", na.MemberInfo.Name, na.TypedValue.ArgumentType, na.TypedValue.Value);
}
Console.WriteLine();
}
Console.WriteLine();
}
}