C# 图解教程 第5版 —— 第25章 反射和特性

25.1 元数据和反射

​ 有些程序处理的数据不是数字、文本或图形,而是关于程序和程序类型的信息。

  • 有关程序及其类型的数据被称为元数据,保存在程序的程序集中。

  • 程序运行时,可以查看其他程序集或其本身的元数据。这种行为叫做反射

  • 要使用反射,必须使用 System.Reflection 命名空间。

25.2 Type 类

​ BCL 声明了一个 Type 抽象类(不能有实例),用来包含类型的特征,获取程序使用的类型信息。

​ 在运行时,CLR 创建从 Type 派生的类(RuntimeType)的实例,包含类型信息。访问这些实例时,CLR 不会返回派生类的引用,而是返回 Type 类的引用。方便起见,本章将引用指向的对象称为 Type 类型的对象。

  • 程序中用到的每一个类型,CLR 都会创建一个包含这个类型信息的 Type 类型对象。
  • 同一类型的所有实例只被一个 type 对象关联。
image-20240114140313995
图25.1 对于程序中使用的每个类型,CLR 都会实例化 Type 类型的对象

​ 表 25.1 列出了 Type 类中常用的成员。

表25.1 System.Type 类的部分成员
image-20240114140613155

25.3 获取 Type 对象

使用 GetType 方法

​ object 类型包含方法 GetType,返回示例的 Type 对象引用。由于每个类型都是由 object 派生的,因此可以在任何类型对象上使用 GetType 方法。

image-20240114141117010

使用 typeof 运算符

​ 提供类型名作为操作数,typeof 就会返回 Type 对象的引用。

image-20240114141206405

25.4 什么是特性

​ 特性是一种允许向程序集添加元数据的语言结构,用于保存程序结构信息的特殊类型。

  • 将应用了特性的程序结构称为目标
  • 设计用来获取和使用元数据的程序称为特性的消费者
  • .NET 预定义了许多特性,也可以自己声明自定义特性。
image-20240114141409949
图25.2 创建和使用特性的相关组件
  • 在源代码中将特性应用于程序结构。
  • 编译器获取源代码并从特性产生元数据,之后将元数据放到程序集中。
  • 消费者程序可以获取特性的元数据以及程序中其他组件的元数据。即,编译器同时生产和消费特性。
  • 特性名使用 Pascal 命名法并以 Attribute 后缀结尾。

25.5 应用特性

  • 通过在结构前防止特性片段来应用特性。
  • 特性片段由方括号包围特性名和参数列表(可有可无)构成。
  • 大多数特性只应用于直接跟随在一个或多个特性片段后的结构。
  • 引用了特性的结构称为被特性装饰(decorated 或 adorned)。
image-20240114141747207

25.6 预定义的保留特性

25.6.1 Obsolete 特性

​ 使用 Obsolete 特性将程序结构标注为“过时”,并可以提供相关的警告信息。

image-20240114142007760

​ 程序可以正常运行,但是编译器会产生一条警告信息:

image-20240114142047163

​ 另外,可以通过改变第二个参数为 true,将代码标记为错误而不是警告。

image-20240114142120814

25.6.2 Conditional 特性

​ Conditional 特性允许包括或排斥指定方法的所有调用,使用该特性时,需要将一个编译符号作为参数。

  • 如果定义了编译符号,则编译器会包含所有调用这个方法的代码。
  • 如果没有定义编译符号,编译器将忽略代码中这个方法的所有调用。
  • 方法本身的 CIL 代码会包含在程序集中,只是调用时会被忽略。
  • 除了应用在方法上,Conditional 特性还可以引用在类上,这里不做介绍。

​ Conditional 特性应用的方法必须满足以下条件:

  1. 必须是类或结构体的方法。
  2. 必须为 void 方法。
  3. 不能被声明为 override,但可以是 virtual。
  4. 不能是接口方法的实现。
image-20240114142543690

​ 当编译器编译上述代码时,会检查是否定义了编译符号 DoTrace。

  • 若定义,则编译器和往常一样包含这些方法的调用。
  • 若未定义,则编译器不会输出任何对 TraceMessage 的任何调用代码。

25.6.3 调用者信息特性

​ 利用调用者信息特性可以访问文件路径、代码行数、调用成员的名称等源代码信息。

  • 这三个特性分别为:
    1. CallerFilePath。
    2. CallerLineNumber。
    3. CallerMemberName。
  • 上述特性只能用于方法中的可选参数。
image-20240114142852125 image-20240114142900158

25.6.4 DebuggerStepThrough 特性

​ DebuggerStepThrough 特性告诉调试器在执行目标代码时不要进入该方法调试。

  • 该特性位于 Sustem.Diagnostics 命名空间。
  • 该特性可用于类、结构、构造函数、方法或访问器。

25.6.5 其他预定义特性

表25.2 .NET 中定义的重要特性
image-20240114143102018

25.7 关于应用特性的更多内容

25.7.1 多个特性

​ 可以为单个结构应用多个特性。

  • 多个特性可以使用下面任何一种格式列出:
    • 独立的特性片段。
    • 单个特性片段,特性之间使用逗号分隔。
  • 可以以任何次序列出特性。
image-20240114143251070

25.7.2 其他类型的目标

​ 可以将特性应用到其他程序结构,并可以显示地标注特性。

image-20240114143345434
表25.3 特性目标
image-20240114143354899

25.7.3 全局特性

​ 可以使用 assembly 和 module 目标名称来使用显式目标说明符将特性设置在程序集或模块级别。

  • 程序集级别的特性必须防止在任何命名空间之外,并且通常放置在 AssemblyInfo.cs 文件中。
  • AssemblyInfo.cs 文件通常包含有关公司、产品以及版权信息的元数据。
image-20240114143649490 image-20240114143703590

25.8 自定义特性

​ 特性只是一种特殊的类:

  • 用户自定义的特性类称为自定义特性。
  • 所有特性类都派生自 System.Atrribute。

25.8.1 声明自定义特性

  • 声明一个自定义特性,需要做如下工作:
    • 声明一个派生自 System.Attribute 的类。
    • 起一个以后缀 Attribute 结尾的名称。
  • 安全起见,建议声明的特性类为 sealed。
  • 由于特性持有目标的信息,所有特性类的公有成员只能是:
    • 字段。
    • 属性。
    • 构造函数。
image-20240114143946786

25.8.2 使用特性的构造函数

​ 每个特性必须至少有一个公共构造函数。

  • 和其他类一样,如果不声明构造函数,编译器会产生一个隐式公共无参的构造函数。
  • 特性的构造函数和其他构造函数一样,可以被重载。
  • 声明构造函数时必须使用类全名,包括后缀。只可以在应用特性时使用短名称。
image-20240114144154829

25.8.3 指定构造函数

​ 在为目标应用特性时,其实在指定应该使用哪个构造函数来创建特性实例。

image-20240114144258315
  • 应用特性时,构造函数的实参必须在编译时就能确定值。
  • 如果应用的特性构造函数没有参数,可以省略圆括号。
image-20240114144342040

25.8.4 使用构造函数

​ 和其他类一样,不能显式调用构造函数。特性的实例被创建后,只有特性的消费者访问特性时才能调用构造函数。因此,应用一个特性是一条声明语句,只决定使用哪个构造函数创建特性,而不会当即创建特性。

  • 命令语句的意义是:“在这里创建新的类”。
  • 声明语句的意义是:“这个特性和这个目标相关联,如果需要创建特性,则使用这个构造函数”。
image-20240114144618792
图25.3 比较构造函数的使用

25.8.5 构造函数中的位置参数和命名参数

​ 与普通类的方法和构造函数蕾西,特性的构造函数同样可以使用位置参数和命名参数,且位置参数必须放在命名参数之前。

image-20240114144818907

25.8.6 限制特性的使用

​ 使用预定义特性 AttributeUsage 来限制自定义特性的使用范围。

image-20240114144957407

​ AttributeUsage 有 3 个重要的公有属性,如表 25.4 所示。

表25.4 AttributeUsage 的公有属性
image-20240114145117427

AttributeUsage 的构造函数

​ AttributeUsage 的构造函数接受单个位置参数,该参数设置 ValidOn 属性,指定可使用特性的目标类型。

image-20240114145754641

​ 可接受的目标类型是 AttributeTargets 枚举的成员,枚举的所有成员如表 25.5 所示。

表25.5 AttributeTargets 枚举的成员
image-20240114145740137

​ 下面示例中的 MyAttribute 只能应用在类上,却不会被应用它的类的派生类继承。

image-20240114145958787

25.8.7 自定义特性的最佳实践

​ 建议参考如下示例编写自定义特性:

  1. 特性类应明确表示目标结构的某种状态。
  2. 除了属性外,不要实现共有方法或其他函数成员。
  3. 为了更安全,将特性类声明为 sealed。
  4. 在特性声明中使用 AttributeUsage 来显式指定特性目标组。
image-20240114150021225

25.9 访问特性

​ 使用 Type 对象的方法来获取自定义特性。

25.9.1 使用 IsDefined 方法

  • 第一个参数接受需要检查的特性的 Type 对象。
  • 第二个参数为 bool 类型,指示是否搜索 MyClass 继承树来查找该特性。
image-20240114150450998 image-20240114150459008

25.9.2 使用 GetCustomAttributes 方法

  • 实际返回的对象是 object 数组,因此必须将其强制转换为相应的特性类型。
  • 布尔参数指定是否搜索继承树来查找特性。
image-20240114150617461
  • 调用 GetCustomAttributes 方法后,每个与目标关联的特性示例就会被创建。
image-20240114150814081 image-20240114150707277 image-20240114150828681
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔗理苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值