文章目录
-
- 25.1 元数据和反射
- 25.2 Type 类
- 25.3 获取 Type 对象
- 25.4 什么是特性
- 25.5 应用特性
- 25.6 预定义的保留特性
-
- 25.6.1 Obsolete 特性
- 25.6.2 Conditional 特性
- 25.6.3 调用者信息特性
- 25.6.4 DebuggerStepThrough 特性
- 25.6.5 其他预定义特性
- 25.7 关于应用特性的更多内容
-
- 25.7.1 多个特性
- 25.7.2 其他类型的目标
- 25.7.3 全局特性
- 25.8 自定义特性
-
- 25.8.1 声明自定义特性
- 25.8.2 使用特性的构造函数
- 25.8.3 指定构造函数
- 25.8.4 使用构造函数
- 25.8.5 构造函数中的位置参数和命名参数
- 25.8.6 限制特性的使用
- 25.8.7 自定义特性的最佳实践
- 25.9 访问特性
-
- 25.9.1 使用 IsDefined 方法
- 25.9.2 使用 GetCustomAttributes 方法
25.1 元数据和反射
有些程序处理的数据不是数字、文本或图形,而是关于程序和程序类型的信息。
-
有关程序及其类型的数据被称为元数据,保存在程序的程序集中。
-
程序运行时,可以查看其他程序集或其本身的元数据。这种行为叫做反射。
-
要使用反射,必须使用 System.Reflection 命名空间。
25.2 Type 类
BCL 声明了一个 Type 抽象类(不能有实例),用来包含类型的特征,获取程序使用的类型信息。
在运行时,CLR 创建从 Type 派生的类(RuntimeType)的实例,包含类型信息。访问这些实例时,CLR 不会返回派生类的引用,而是返回 Type 类的引用。方便起见,本章将引用指向的对象称为 Type 类型的对象。
- 程序中用到的每一个类型,CLR 都会创建一个包含这个类型信息的 Type 类型对象。
- 同一类型的所有实例只被一个 type 对象关联。
图25.1 对于程序中使用的每个类型,CLR 都会实例化 Type 类型的对象
表 25.1 列出了 Type 类中常用的成员。
表25.1 System.Type 类的部分成员
25.3 获取 Type 对象
使用 GetType 方法
object 类型包含方法 GetType,返回示例的 Type 对象引用。由于每个类型都是由 object 派生的,因此可以在任何类型对象上使用 GetType 方法。
使用 typeof 运算符
提供类型名作为操作数,typeof 就会返回 Type 对象的引用。
25.4 什么是特性
特性是一种允许向程序集添加元数据的语言结构,用于保存程序结构信息的特殊类型。
- 将应用了特性的程序结构称为目标。
- 设计用来获取和使用元数据的程序称为特性的消费者。
- .NET 预定义了许多特性,也可以自己声明自定义特性。
图25.2 创建和使用特性的相关组件
- 在源代码中将特性应用于程序结构。
- 编译器获取源代码并从特性产生元数据,之后将元数据放到程序集中。
- 消费者程序可以获取特性的元数据以及程序中其他组件的元数据。即,编译器同时生产和消费特性。
- 特性名使用 Pascal 命名法并以 Attribute 后缀结尾。
25.5 应用特性
- 通过在结构前防止特性片段来应用特性。
- 特性片段由方括号包围特性名和参数列表(可有可无)构成。
- 大多数特性只应用于直接跟随在一个或多个特性片段后的结构。
- 引用了特性的结构称为被特性装饰(decorated 或 adorned)。
25.6 预定义的保留特性
25.6.1 Obsolete 特性
使用 Obsolete 特性将程序结构标注为“过时”,并可以提供相关的警告信息。
程序可以正常运行,但是编译器会产生一条警告信息:
另外,可以通过改变第二个参数为 true,将代码标记为错误而不是警告。
25.6.2 Conditional 特性
Conditional 特性允许包括或排斥指定方法的所有调用,使用该特性时,需要将一个编译符号作为参数。
- 如果定义了编译符号,则编译器会包含所有调用这个方法的代码。
- 如果没有定义编译符号,编译器将忽略代码中这个方法的所有调用。
- 方法本身的 CIL 代码会包含在程序集中,只是调用时会被忽略。
- 除了应用在方法上,Conditional 特性还可以引用在类上,这里不做介绍。
Conditional 特性应用的方法必须满足以下条件:
- 必须是类或结构体的方法。
- 必须为 void 方法。
- 不能被声明为 override,但可以是 virtual。
- 不能是接口方法的实现。
当编译器编译上述代码时,会检查是否定义了编译符号 DoTrace。
- 若定义,则编译器和往常一样包含这些方法的调用。
- 若未定义,则编译器不会输出任何对 TraceMessage 的任何调用代码。
25.6.3 调用者信息特性
利用调用者信息特性可以访问文件路径、代码行数、调用成员的名称等源代码信息。
- 这三个特性分别为:
- CallerFilePath。
- CallerLineNumber。
- CallerMemberName。
- 上述特性只能用于方法中的可选参数。
25.6.4 DebuggerStepThrough 特性
DebuggerStepThrough 特性告诉调试器在执行目标代码时不要进入该方法调试。
- 该特性位于 Sustem.Diagnostics 命名空间。
- 该特性可用于类、结构、构造函数、方法或访问器。
25.6.5 其他预定义特性
表25.2 .NET 中定义的重要特性
25.7 关于应用特性的更多内容
25.7.1 多个特性
可以为单个结构应用多个特性。
- 多个特性可以使用下面任何一种格式列出:
- 独立的特性片段。
- 单个特性片段,特性之间使用逗号分隔。
- 可以以任何次序列出特性。
25.7.2 其他类型的目标
可以将特性应用到其他程序结构,并可以显示地标注特性。
表25.3 特性目标
25.7.3 全局特性
可以使用 assembly 和 module 目标名称来使用显式目标说明符将特性设置在程序集或模块级别。
- 程序集级别的特性必须防止在任何命名空间之外,并且通常放置在 AssemblyInfo.cs 文件中。
- AssemblyInfo.cs 文件通常包含有关公司、产品以及版权信息的元数据。
25.8 自定义特性
特性只是一种特殊的类:
- 用户自定义的特性类称为自定义特性。
- 所有特性类都派生自 System.Atrribute。
25.8.1 声明自定义特性
- 声明一个自定义特性,需要做如下工作:
- 声明一个派生自 System.Attribute 的类。
- 起一个以后缀 Attribute 结尾的名称。
- 安全起见,建议声明的特性类为 sealed。
- 由于特性持有目标的信息,所有特性类的公有成员只能是:
25.8.2 使用特性的构造函数
每个特性必须至少有一个公共构造函数。
- 和其他类一样,如果不声明构造函数,编译器会产生一个隐式公共无参的构造函数。
- 特性的构造函数和其他构造函数一样,可以被重载。
- 声明构造函数时必须使用类全名,包括后缀。只可以在应用特性时使用短名称。
25.8.3 指定构造函数
在为目标应用特性时,其实在指定应该使用哪个构造函数来创建特性实例。
- 应用特性时,构造函数的实参必须在编译时就能确定值。
- 如果应用的特性构造函数没有参数,可以省略圆括号。
25.8.4 使用构造函数
和其他类一样,不能显式调用构造函数。特性的实例被创建后,只有特性的消费者访问特性时才能调用构造函数。因此,应用一个特性是一条声明语句,只决定使用哪个构造函数创建特性,而不会当即创建特性。
- 命令语句的意义是:“在这里创建新的类”。
- 声明语句的意义是:“这个特性和这个目标相关联,如果需要创建特性,则使用这个构造函数”。
图25.3 比较构造函数的使用
25.8.5 构造函数中的位置参数和命名参数
与普通类的方法和构造函数蕾西,特性的构造函数同样可以使用位置参数和命名参数,且位置参数必须放在命名参数之前。
25.8.6 限制特性的使用
使用预定义特性 AttributeUsage 来限制自定义特性的使用范围。
AttributeUsage 有 3 个重要的公有属性,如表 25.4 所示。
表25.4 AttributeUsage 的公有属性
AttributeUsage 的构造函数
AttributeUsage 的构造函数接受单个位置参数,该参数设置 ValidOn 属性,指定可使用特性的目标类型。
可接受的目标类型是 AttributeTargets 枚举的成员,枚举的所有成员如表 25.5 所示。
表25.5 AttributeTargets 枚举的成员
下面示例中的 MyAttribute 只能应用在类上,却不会被应用它的类的派生类继承。
25.8.7 自定义特性的最佳实践
建议参考如下示例编写自定义特性:
- 特性类应明确表示目标结构的某种状态。
- 除了属性外,不要实现共有方法或其他函数成员。
- 为了更安全,将特性类声明为 sealed。
- 在特性声明中使用 AttributeUsage 来显式指定特性目标组。
25.9 访问特性
使用 Type 对象的方法来获取自定义特性。
25.9.1 使用 IsDefined 方法
- 第一个参数接受需要检查的特性的 Type 对象。
- 第二个参数为 bool 类型,指示是否搜索 MyClass 继承树来查找该特性。
25.9.2 使用 GetCustomAttributes 方法
- 实际返回的对象是 object 数组,因此必须将其强制转换为相应的特性类型。
- 布尔参数指定是否搜索继承树来查找特性。
- 调用 GetCustomAttributes 方法后,每个与目标关联的特性示例就会被创建。