前面介绍了如何在程序的各个数据项上定义特性。这些特性都是Microsoft定义好的,作为.NET Framework类库的一部分,许多特性都得到了C#编译器的支持。对于这些特殊的特性,编译器可以以特殊的方式定义编译过程,例如,可以根据StructLayout特性中的信息在内存中布置结构。
.NET Framework也允许用户定义自己的特性。显然,这些特性不会影响编译过程,因为编译器不能识别它们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据。
这些元数据在文档说明中非常有用。但是,使用自定义特性非常强大的因素是使用反射,代码可以读取这些元数据,使用它们在运行期间做出决策。也就是说,自定义特性可以直接影响代码运行的方式。例如,自定义特性可以用于支持对自定义许可类进行声明性的代码访问安全检查,把信息与程序元素关联起来,程序元素由测试工具使用,或者在开发可扩展的架构时,允许加载插件或模块。
1.编写自定义特性
为了理解编写自定义特性的方式,应了解以下在编译器遇到代码中某个应用了自定义特性的元素时,该如何处理。以数据库为例,假定有一个C#属性声明,如下所示。
[FieldName("SocialSecurityNumber")]
public string SocialSecurityNumber
{
//get{};
//set{};
}
当C#编译器发现这个属性(property)应用了一个FieldName特性时,首先会把字符串Attribute追加到这个名称的后面,形成一个组合名称FieldNameAttribute,然后在其搜索路径的所有名称空间(即在using语句中提及的名称空间)中搜索有指定名称的类。但要注意,如果用一个特性标记数据项,而该特性的名称以字符串Attribute结尾,编译器就不会把该字符串加到组合名称中,而是不修改该特姓名。因此,上面的代码等价于:
[FieldNameAttribute("SocialSecurityNumber")]
public string SocialSecurityNumber
{
//get{};
//set{};
}
编译器会找到含有该名称的类,且这个类直接或间接派生自System.Attribute。编译器还认为这个类包含控制特性用法的信息。特别是属性类需要指定:
- 特性可以应用到哪些类型的程序元素上(类、结构、属性和方法等)
- 它是否可以多次应用到同一个程序元素上
- 特性在应用到类或接口上时,是否由派生类和接口继承
- 这个特性有哪些必选和可选参数
如果编辑器找不到对应的特性类,或者找到了一个特性类,但是用特性的方式与特性类中的信息不匹配,编译器就会产生一个编译错误。例如,如果特性类指定该特定只能应用于类,但我们把它应用到结构定义上,就会产生一个编译错误。
继续上面的示例,假定定义了一个FieldName特性:
[AttributeUsage(AttributeTargets.Property,AllowMultiple = false,Inherited = false)]
public class FieldNameAttribute:Attribute
{
private string _name;
public FieldNameAttribute(string name)
{
_name