《CLR via C#》 特性

定制特性

自定义特性其实就是将一些附加信息与某个目标元素关联起来的方式。编译器在托管模块的元数据中生成(嵌入)这些额外的信息。

定制特性其实是一个类型的实例。定制特性类必须直接或间接从公共抽象类System.Attribute派生

定制特性类必须有构造器才能创建实例,所以将特性应用于目标元素时,语法类似于调用类的某个实例构造器。构造器的参数称为定位参数(positional parameter),而且是强制性的,必须指定参数。而其他的公共字段或属性被称为命名参数(named parameter),这种参数是可选的。

 1// 自定义特性
 2public class AttrAttribute : System.Attribute {
 3    public string Name;         
 4    public int Num;
 5    public char Char;
 6    public AttrAttribute(string name) {
 7        Name = name;
 8    }
 9}
10
11// name属于定位参数
12// Num与Char属于命名参数
13[Attr("String", Num = 1, Char = 'c')] 
14public void Func() {
15
16}

提示

将特性应用于源代码中的目标元素时,C#编译器允许省略Attribute后缀以减少打字量,并提升源代码的可读性。

CLR允许将特性应用于可在文件的元数据中表示的几乎任何东西。C#只允许将特性应用于定义以下任何目标元素的源代码:程序集、模块、类型(类、结构、枚举、接口、委托)、字段、方法(含构造器)、方法参数、方法返回值、属性、事件和泛型类型参数。应用特性时,C#允许使用一个前缀明确指定特性要应用于的目标元素。

 1using System;
 2[assembly: SomeAttr]                    // 应用于程序集
 3[module: SomeAttr]                      // 应用于模块
 4[type: SomeAttr]                        // 应用于类型
 5internal sealed class SomeType<[typevar: SomeAttr] T> { // 应用于泛型类型变量
 6    [field: SomeAttr]                   // 应用于字段
 7    public int SomeField = 0;
 8    [return: SomeAttr]                  // 应用于返回值
 9    [method: SomeAttr]                  // 应用于方法
10    public int SomeMethod([param: SomAttr] int SomeParam) { // 应用于参数
11        return SomeParam;
12    }
13    [property: SomAttr]                 // 应用于属性
14    public string SomeProp {
15        [method: SomeAttr]              // 应用于get访问器方法
16        get { return null; }
17    }
18    [event: SomeAttr]                   // 应用于事件
19    [field: SomeAttr]                   // 应用于编译器生成的字段
20    [method: SomeAttr]                  // 应用于编译器生成的add和remove方法
21    public event EventHandler SomeEvent;
22}

限制特性的应用范围

向特性应用System.AttributeUsageAttribute特性来限制特性的合法范围

AttributeUsage特性的属性 功能
Inherited 指出特性在应用于基类时,是否同时应用于派生类和重写的方法。默认为true
AllowMultiple 设为true就只能向选定的目标引用一次。默认为false
 1// 将特性限制只能应用于枚举类型
 2[AttributeUsage(AttributeTargets.Enum, Inherited = false)]
 3public class AttrAttribute : System.Attribute {
 4    public string Name;         
 5    public int Num;
 6    public char Char;
 7    public AttrAttribute(string name) {
 8        Name = name;
 9    }
10}

特性构造器和字段/属性数据类型

特性的字段和属性的数据类型只允许基元值类型,StringTypeObject。还可以任意类型的一维0基数组,但使用数组会破坏CLS相容性。

当编译器检测到向目标元素应用了定制特性时,会调用特性类的构造器,向它传递任何指定参数,从而构造特性类的实例。然后编译器采用增强型构造器语法所指定的值,对任何公共字段和属性进行初始化。构造并初始化好定制特性类的对象之后,编译器将它的状态序列化到目标元素的元数据表记录项中。

提示

方便理解定制特性:它是类的实例,被序列化成驻留在元数据中的字节流。运行时可对元数据中的字节进行反序列化,从而构造出类的实例。

定制特性实际发生的事情:编译器在元数据中生成创建特性类的实例所需的信息。每个构造器参数都是1字节的类型ID,后跟具体的值。对构造器的参数进行“序列化”时,编译器先写入字段/属性名称,再跟上1字节的类型ID,最后是具体的值。如果是数组,则会先保存数组元素的个数,再跟上每个单独的元素。

定制特性的应用

System.Reflection.CustomAttributeExtensions类定义的三个静态方法来获取与目标关联的特性:IsDefinedGetCustomAttributesGetCustomAttribute

方法名称 说明
IsDefined 只判断是否应用了一个特性。效率高。因为它不会构造特性对象,不会调用构造器,也不会设置字段和属性。
GetCustomAttributes 返回应用于目标的指定特性对象的集合。每个实例都使用编译时指定的参数、字段和属性来构造(反序列化)。如果目标没有应用指定特性类的实例,就返回空集合。
GetCustomAttribute 同上,如果没有指定实例,就返回null,如果有多个实例则抛出异常。