一、 引言
利用定制的Attribute,我样可以声明性地为自己的代码添加注解,从而实现一些特殊的功能。定制的Attribute允许将定义的信息应用于几乎每一个元数据表记录项(AttributeTargets枚举了可应用的所有项)
当我们将一个定制的Attribute应用于某个元数据,例如应用于某个类时,则在编译后,会在该类的元数据中的CustomerAttribute“注册表”中添加定制的Attribute记录。
因此对于定制的Attribute的真正的用处,是需要两个保证的,其一是编译器将定制的Attribute信息写入元数据,这个保证是基础,其二,既然元数据表记录项的CustomerAttribute “注册表”注册了某些Attribute,则在运行时,我们可动态的获取某个记录项(类、方法、字段等)中应用的Attribute实例,并获取实例中的信息,从而动态的改变代码的执行方式。
.NET的各种技术(Windows窗体、Web窗体、XML Web服务等)它们都应用了定制的Attribute,目的是方便开发者在代码中表达他们的意图
二、 定义Attribute在元数据中的体现
这一节原本是可以略过的,但是我觉得我还是有必要单独在一节里面对我在上面讲到的“编译后,会在该类的元数据中的CustomerAttribute“注册表”中添加定制的Attribute记录“这一句作个解释,了解这个,我觉得对去理解Attriubte的使用,有些很重要的意义。
举个例子来说明,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Attribute_Demo
{
//[AttributeUsage(AttributeTargets.Class)]
public class CClassAttribute : Attribute
{
}
class Program
{
static void Main(string[] args)
{
}
}
}
此时注意CClassAttribute类,此时它没有应用于任何Attribute,我们来看一下编译器对它生成的元数据(ILDASM反编译器打开生成的exe,CTRL+M打开MetaInfo)如下:
然后我们将取消注释的定制属性代码,生成后再次查看CClassAttribute的元数据,如下:
我们对比一下CClassAttribute类型应用了属性AttributeUsageAttribute前后生成的元数据,显然编译器在生成应用了定制属性的AttributeUsageAttribute的CClassAttrubte元数据时,会将定制属性记录到CClassAttriubte的CustomAttribute的“注册表”中。
因此,对于类型、方法、字段等,可以在运行时,从它们的CustomAttribute的“注册表“中,查询是否应用了某些定制属性,从而动态的改变代码的执行方式。
三、 使用定制Attribute
C#允许将attribute应用于对以下任何目标元素进行定义的源代码:程序集、模块、类型(类、结构、委托、枚举、接口)、字段、方法(含构造器)、方法参数、方法返回值、属性、事件和泛型参数)
应用一个attribute时,C#允许一个前缀指定attribute应于的目标元素,许多情况,我们可以省略前缀,编译器一样能判断一个attribute应用于的元素目标,但有些情况,必须要指定一个前缀,来指定编译器清楚我们的意图,例如,当一个属性应用于事件时,如果不指定一个前缀,则编译器默认该属性只应用于事件,然而我们原本的意思是要将该属性应用于字段(事件的本质是一个私有的委托字段和Add和Remove方法)
下面的代码中,详细的例出所有可以应用的前缀,以及不能省略前缀的情况:
[assembly: SomeAttr] //应用于程序集 前缀不能省略
[module: SomeAttr] //应用于模块 前缀可以省略
[type: SomeAttr] //应用于类型 前缀可以省略
internal class SomeType
{
[field: SomeAttr] //应用于字段 前缀可以省略
public Int32 SomeField = 0;
[return: SomeAttr] //应用于返回值 前缀不能省略 如省略编译器默认应用于方法
[method: SomeAttr] //应用于方法 前缀可以省略
public Int32 SomeMethod([param: SomeAttr] //应用于参数 前缀可以省略
Int32 para)
{
return para;
}
[property: SomeAttr] //应用于属性 前缀可以省略
public Int32 SomeProp
{
[method: SomeAttr]//应用于get访问器方法,前缀可以省略
get { return SomeField; }
}
[event: SomeAttr] //应用于事件 前缀可以省略
[field: SomeAttr] //应用于编译器生成的委托字段 前缀不能省略 如省略编译器默认应用于事件
[method: SomeAttr]//应用于编译器生成的add和remove方法,前缀不能省略,如省略编译器默认应用于事件
public event EventHandler SomeEvent;
}
通过上面的示代码示例,我们知道,当attribute应用于某一项时,编译器会默认指定它应用的实际项,例如当一个attribute应用于字段时,此时可以将field前缀省略,因为这个属性只能作用于字段,但如果把一个作用于field的attribute应用于事件时,如果省略field前缀,则编译器默认作用于事件本身,这就与我们原本的意图不相符,因此在这种情况下,我们需要显示指定一个attribute前缀。
在介绍定制的Attribute之前,我们先来了解下attribute的使用规则
1、 定制attribute为必须直接或者间接地从公共抽象类System.Attribute派生
2、 attribute必须有一个公共构造器
3、 attribute可能支持一些特殊的语法,允许你设置与attribute类关联的公共字段或属性
4、 多个attribute可应用于单个目标元素
5、 可将每个attribute都封闭到一对方括号内,也可在一对方括号内以多个逗号分隔的attribute,如果attribute类构造器不获取参数,圆括号就是可有可无
6、 attribute类的后缀”Attribute”是可选的
四、 定义自己的attribute类
我们定义一个attribute类如下:
internal class TableAttribute : Attribute
{
public TableAttribute()
{
}
}
此时我们定义了一个TableAttribute类,此时这个attribute没有什么实际意义
1、 它从Attribute派生
2、 它有一个公共的构造函数
3、 为了保持与标准的相容性,TableAttribute类有一个Attribute后缀,但这不是必须的,所以你完全可以定义类名为TableSomeSuffex或者其他一些名称
最重要的这个TableAttribute可以用于任何目标元素,因此问题出现了,有时候我们想定义一个attribute只用于类,或者字段,而不想它用于方法,这时就需要一个限制,例如.NET的内置的Attribute,FlagAttribute只能用于struct,而不能作用于类或者字段
这时我们有必要去了解一个只能作用于attribute的attribute类---AttributeUsageAttribute
这里有点呦口,意思是指:
它只能作用于Attribute派生的类,这里隐藏了两个信息
1、 它只能作用于类(class)
2、 它只能作用派生自Attribute的类
例如下面的用法,是错误的,编译器会抛出:特性AttributeUsageAttribute仅在从System