CLR笔记:17.自定义属性
17.1 使用自定义属性
只是将一些附加信息与某个目标元素关联起来。编译器在托管模块的元数据中生成额外的信息。
从System.Attribute派生,所有符合CLS的属性都是从这个基类派生。
有定位参数和命名参数两种,前者必须指定。
可以将多个属性应用于单个目标元素,用逗号分割。
17.2 定义自己的属性
属性类标准写法:
public class FlagAttribute : System.Attribute
{
public FlagAttribute() { }
}
注意:1.属性就是类的一个实例,因此属性类至少要有一个公共构造器。如果class没有ctor,就生成默认ctor,所以也可以编译通过。
2.这个类不要提供任何公共方法/事件
3.FlagAttribute使用的时候,可以简写为[Flag]
4.AttributeTarget枚举,限定属性的应用范围,上面程序说明Flag只能用于Enum类型;AttributeTarget.All表示适用于所有类型。
5.AllowMultiple指出是否可以将属性多次应用于单个目标:
大部分属性只能使用一次,如以下代码会编译出错,因为没有任何意义:
[Flag]
public enum Color
{
Red
}
少数属性有必要将属性多次应用于单个目标,如Conditional属性类(见17.7)
6.Inherited指出属性是否能由派生类和重写成员继承,如下代码:
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)如果常量表达式为值类型,则执行时需要装箱。
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类型
这个方法的实现如下:
{
//检查枚举类型是否应用了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方法,返回一个应用于目标的属性数组
{
Attribute[] attributes = Attribute.GetCustomAttributes(attributeTarget);
foreach (Attribute attribute in attributes)
{
//遍历属性数组
}
}
3.GetCustomAttribute方法,返回应用于目标的制定属性类的一个实例,使用方法见下一节。
17.5 两个属性实例的相互匹配
自定义属性,要重写Match()方法,才可以比较两个属性实例,否则,会调用System.Attribute的match()方法,而后者,只是调用Mquals方法。
实例展示了Match的重写,以及上一节GetCustomAttribute方法的使用
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中设置的
{
IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(attributeTarget);
foreach (CustomAttributeData attribute in attributes)
{
//遍历属性数组
}
}
}
17.7 条件属性类:使用了System.Diagnostics.ConditionalAttribute的属性类
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中生成。