购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

4.14 特性

之前我们介绍过,在程序中可以使用修饰符指定代码的行为,例如 virtual ref 。这些结构都是语言内置的。而特性是一种为代码元素(程序集、类型、成员、返回值、参数和泛型类型参数)添加自定义信息的扩展机制。这种扩展机制对C#语言中那些与类型系统深度整合,而无须特殊关键字或结构的服务是非常有用的。

特性的一个常见的例子是序列化或反序列化过程,即将任意对象转换为一个用于存储或传输的特定格式,或从特定格式生成对象的过程。我们可以在字段上使用特性来表明C#字段和特定格式表示方式间的转换关系。

4.14.1 特性类

特性是通过直接或者间接继承 System.Attribute 抽象类来定义的。如果要将特性附加到代码元素中,那么就需要在该代码元素前用方括号指定特性的类型名称。例如,以下的例子将 ObsoleteAttribute 附加到 Foo 类上:

编译器可以识别该特性,并在编译时对引用该特性标记的类型或成员的行为产生警告。按照惯例,所有特性类型都以Attribute结尾。C#能够识别这个后缀,并允许在为成员附加特性时忽略该后缀:

ObsoleteAttribute 是在 System 命名空间中声明的一种类型,如下所示(为了简明起见省略部分代码):

C#语言和.NET库中包含了大量的预定义特性,我们将在第18章介绍如何自定义特性。

4.14.2 命名和位置特性参数

特性可以包含参数。在下面的例子中,我们将 XmlTypeAttribute 添加到一个类上。这个特性指示(在 System.Xml.Serialization 命名空间内定义的)XML序列化器如何将一个对象转化为XML格式并接受哪些特性参数。以下代码中的特性将 CustomerEntity 类映射到名为 Customer 的XML元素上,且这个XML元素位于 http://oreilly.com 命名空间:

特性参数分为位置参数和命名参数。在前一个例子中,第一个参数是位置参数,第二个参数是命名参数。位置参数对应特性类中公有构造器的参数,命名参数则对应特性类中的公有字段或者公有属性。

当指定一个特性时,必须包含特性构造器中的位置参数,而命名参数则是可选的。

我们将在第18章中详细介绍有效的参数类型,以及这些参数的求值规则。

4.14.3 在程序集与支持字段上使用特性

在不显式指定的情况下,特性的目标就是它后面紧跟的代码元素,并且一般是类型或者类型的成员。然而,也可以将特性附加在程序集上。这就要求显式指定特性的目标了。以下代码展示了如何在程序集中添加 AssemblyFileVersion 特性以指定程序集的版本信息:

从C# 7.3开始,可以使用 field: 前缀为自动属性的支持字段添加特性。该功能可用于序列化的控制:

4.14.4 在Lambda表达式上使用特性(C# 10)

从C# 10开始,我们可以在Lambda表达式的方法、参数和返回值上使用特性。例如:

当我们使用某些需要在方法上添加特性的框架时——例如ASP.NET——上述功能就显得尤为重要了。通过在Lambda表达式上添加特性,我们就不必为简单的操作创建命名方法了。

这些特性将附加在那些编译器生成的方法上,而委托变量则指向这些方法。在第18章中,我们将介绍如何通过反射使用代码中的特性。在具体介绍之前,我们可以直接使用其结论:

为了避免语法上的二义性,在Lambda表达式的参数上使用特性时必须添加括号。此外,若Lambda表达式作为表达式树存在,则不能在其上附加特性。

4.14.5 指定多个特性

一个代码元素可以指定多个特性。特性可以列在同一对方括号中(用逗号分隔),或者分隔在多对方括号中,当然也可以是两种形式的结合。下面的三个例子在语义上是相同的: 6PKPk6twfU4jb5uXRZVMU44mNICaJUIhsCM1zCoYUZj4rn0UkAgd7rXGgA40QyFt

点击中间区域
呼出菜单
上一章
目录
下一章
×