C# 语言规范_版本5.0 (第17章 特性)

时间:2023-03-08 19:47:35

1. 特性

C# 语言的一个重要特征是使程序员能够为程序中定义的实体指定声明性信息。例如,类中方法的可访问性是通过使用 method-modifiers(public、protected、internal 和 private)加以修饰来指定的。

C# 使程序员可以创造新的声明性信息的种类,称为特性 (attributes)。然后,程序员可以将这种特性附加到各种程序实体,而且在运行时环境中还可以检索这些特性信息。例如,一个框架可以定义一个名为 HelpAttribute 的特性,该特性可以放在某些程序元素(如类和方法)上,以提供从这些程序元素到其文档说明的映射。

特性是通过特性类(第 17.1 节)的声明定义的,该声明可以具有定位和命名参数(第 17.1.2 节)。特性是使用特性说明(第 17.2 节)附加到 C# 程序中的实体上的,而且可以在运行时作为特性实例(第 17.3 节)来检索。

1.1 特性类

从抽象类 System.Attribute 派生的类(不论是直接的还是间接的)都称为特性类 (attribute class)。一个关于特性类的声明定义一种新特性 (attribute),它可以放置在其他声明上。按照约定,特性类的名称均带有 Attribute 后缀。使用特性时可以包含或省略此后缀。

1.1.1 特性用法

特性 AttributeUsage(第 17.4.1 节)用于描述使用特性类的方式。

AttributeUsage 具有一个定位参数(第 17.1.2 节),该参数使特性类能够指定自己可以用在哪种声明上。下面的示例

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute: Attribute
{
...
}

定义了一个名为 SimpleAttribute 的特性类,此特性类只能放在 class-declaration 和 interface-declaration 上。下面的示例

[Simple] class Class1 {...}

[Simple] interface Interface1 {...}

演示了 Simple 特性的几种用法。虽然此特性是用名称 SimpleAttribute 定义的,但在使用时可以省略 Attribute 后缀,从而得到简称 Simple。因此,上例在语义上等效于:

[SimpleAttribute] class Class1 {...}

[SimpleAttribute] interface Interface1 {...}

AttributeUsage 还具有一个名为 AllowMultiple 的命名参数(第 17.1.2 节),此参数用于说明对于某个给定实体,是否可以多次指定该特性。如果特性类的 AllowMultiple 为 true,则此特性类是多次性特性类 (multi-use attribute class),可以在一个实体上多次被指定。如果特性类的 AllowMultiple 为 false 或未指定,则此特性类是一次性特性类 (single-use attribute class),在一个实体上最多只能指定一次。

下面的示例

using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple
= true)]
public class AuthorAttribute: Attribute
{
private string name;

public
AuthorAttribute(string name) {
     this.name = name;
}

public
string Name {
     get { return name; }
}
}

定义一个多次使用的特性类,名为 AuthorAttribute。下面的示例

[Author("Brian Kernighan"),
Author("Dennis Ritchie")]
class Class1
{
...
}

演示了一个两次使用 Author 特性的类声明。

AttributeUsage 具有另一个名为 Inherited 的命名参数,此参数指示在基类上指定该特性时,该特性是否也会被从此基类派生的类所继承。如果特性类的 Inherited 为 true,则该特性会被继承。如果特性类的 Inherited 为 false,则该特性不会被继承。如果该值未指定,则其默认值为 true。

没有附加 AttributeUsage 特性的特性类 X,例如

using System;

class X: Attribute {...}

等效于下面的内容:

using System;

[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X: Attribute {...}

1.1.2 定位和命名参数

特性类可以具有定位参数
(positional parameter) 和命名参数 (named parameter)。特性类的每个公共实例构造函数为该特性类定义一个有效的定位参数序列。特性类的每个非静态公共读写字段和属性为该特性类定义一个命名参数。

下面的示例

using System;

[AttributeUsage(AttributeTargets.Class)]
public class HelpAttribute: Attribute
{
public HelpAttribute(string url) {     // Positional parameter
     ...
}

public string Topic {                     // Named parameter
     get {...}
     set {...}
}

public string Url {
     get {...}
}
}

定义了一个名为 HelpAttribute 的特性类,它具有一个定位参数
(url) 和一个命名参数 (Topic)。虽然 Url 属性是非静态的和公共的,但由于它不是读写的,因此它并不定义命名参数。

此特性类可以如下方式使用:

[Help("http://www.mycompany.com/.../Class1.htm")]
class Class1
{
...
}

[Help("http://www.mycompany.com/.../Misc.htm",
Topic = "Class2")]
class Class2
{
...
}

1.1.3 特性参数类型

特性类的定位参数和命名参数的类型仅限于特性参数类型 (attribute parameter type),它们是:

  • 以下类型之一:bool、byte、char、double、float、int、long、sbyte、short、string、uint、ulong、ushort。
  • object 类型。
  • System.Type 类型。
  • 枚举类型,前提是该枚举类型具有 public 可访问性,而且所有嵌套着它的类型(如果有)也必须具有 public 可访问性(第 17.2 节)。
  • 以上类型的一维数组。

不是这些类型的构造函数实参或公共字段不能用作特性规范中的位置或命名形参。

1.2 特性说明

特性规范
(Attribute specification) 就是将以前定义的特性应用到某个声明上。特性本身是一段附加说明性信息,可以把它指定给某个声明。可以在全局范围指定特性(即,在包含程序集或模块上指定特性),也可以为下列各项指定特性:type-declaration(第 9.6 节)、class-member-declaration(第 10.1.5 节)、interface-member-declaration(第 13.2 节)、struct-member-declaration(第 11.2 节)、enum-member-declaration(第 14.3 节)、accessor-declaration(第 10.7.2 节)、event-accessor-declarations(第 10.8.1 节)和 formal-parameter-lists(第 10.6.1 节)。

特性是在特性节
(attribute section) 中指定的。特性节由一对方括号组成,此方括号括着一个用逗号分隔的、含有一个或多个特性的列表。在这类列表中以何种顺序指定特性,以及附加到同一程序实体的特性节以何种顺序排列等细节并不重要。例如,特性说明 [A][B]、[B][A]、[A, B] 和 [B, A] 是等效的。

global-attributes:
global-attribute-sections

global-attribute-sections:
global-attribute-section
global-attribute-sections  
global-attribute-section

global-attribute-section:
[  
global-attribute-target-specifier  
attribute-list   ]
[   global-attribute-target-specifier   attribute-list   ,  
]

global-attribute-target-specifier:
global-attribute-target   :

global-attribute-target:
assembly
module

attributes:
attribute-sections

attribute-sections:
attribute-section
attribute-sections   attribute-section

attribute-section:
[  
attribute-target-specifieropt   attribute-list   ]
[   attribute-target-specifieropt   attribute-list   ,  
]

attribute-target-specifier:
attribute-target   :

attribute-target:
field
event
method
param
property
return
type

attribute-list:
attribute
attribute-list   ,   attribute

attribute:
attribute-name   attribute-argumentsopt

attribute-name:
 type-name

attribute-arguments:
(   positional-argument-listopt   )
(   positional-argument-list   ,  
named-argument-list   )
(   named-argument-list   )

positional-argument-list:
positional-argument
positional-argument-list   ,   positional-argument

positional-argument:
argument-nameopt   attribute-argument-expression

named-argument-list:
named-argument
named-argument-list   ,   named-argument

named-argument:
identifier   =  
attribute-argument-expression

attribute-argument-expression:
expression

如上所述,特性由一个 attribute-name 和一个可选的定位和命名参数列表组成。定位参数(如果有)列在命名参数前面。定位参数包含一个 attribute-argument-expression;命名参数包含一个名称,名称后接一个等号和一个 attribute-argument-expression,这两种参数都受简单赋值规则约束。命名参数的排列顺序无关紧要。

attribute-name 用于标识特性类。如果 attribute-name 的形式等同于一个 type-name,则此名称必须引用一个特性类。其他情况下,将发生编译时错误。下面的示例

class Class1
{}

[Class1]
class Class2 {}  // Error

产生编译时错误,因为它试图将 Class1
用作特性类,而 Class1 并不是一个特性类。

某些上下文允许将一个特性指定给多个目标。程序中可以利用 attribute-target-specifier 来显式地指定目标。特性放置在全局级别中时,则需要 global-attribute-target-specifier。对于所有其他位置上的特性,则采用系统提供的合理的默认值,但是在某些目标不明确的情况下可以使用 attribute-target-specifier 来确认或重写默认值,也可以在目标明确的情况下使用特性目标说明符来确认默认值。因此,除在全局级别之外,通常可以省略 attribute-target-specifier。对于可能造成不明确性的上下文,按下述规则处理:

  • 在全局范围指定的特性可以应用于目标程序集或目标模块。系统没有为此上下文提供默认形式,所以在此上下文中始终需要一个 attribute-target-specifier。如果存在 assembly attribute-target-specifier,则表明此特性适用于指定的目标程序集;如果存在 module attribute-target-specifier,则表明此特性适用于指定的目标模块。
  • 在委托声明上指定的特性,或者适用于所声明的委托,或者适用于它的返回值。如果不存在 attribute-target-specifier,则此特性适用于该委托。如果存在 type attribute-target-specifier,则表明此特性适用于该委托;如果存在 return attribute-target-specifier,则表明此特性适用于返回值。
  • 在方法声明上指定的特性,或者适用于所声明的方法,或者适用于它的返回值。如果不存在 attribute-target-specifier,则此特性适用于方法。如果存在 method attribute-target-specifier,则表明此特性适用于方法;如果存在 return attribute-target-specifier,则表明此特性适用于返回值。
  • 在运算符声明上指定的特性,或者适用于所声明的运算符,或者适用于它的返回值。如果不存在 attribute-target-specifier,则此特性适用于该运算符。如果存在 method attribute-target-specifier,则表明此特性适用于该运算符;如果存在 return attribute-target-specifier,则表明此特性适用于返回值。
  • 对于在省略了事件访问器的事件声明上指定的特性,它的目标对象有三种可能的选择:所声明的事件;与该事件关联的字段(如果该事件是非抽象事件);与该事件关联的 add 和 remove 方法。如果不存在 attribute-target-specifier,则此特性适用于该事件。如果存在 event attribute-target-specifier,则表明此特性适用于该事件;如果存在 field attribute-target-specifier,则表明此特性适用于该字段;而如果存在 method attribute-target-specifier,则表明此特性适用于这些方法。
  • 在属性或索引器声明中的 get 访问器声明上指定的特性,或者适用于该访问器关联的方法,或者适用于它的返回值。如果不存在 attribute-target-specifier,则此特性适用于方法。如果存在 method attribute-target-specifier,则表明此特性适用于方法;如果存在 return attribute-target-specifier,则表明此特性适用于返回值。
  • 在属性或索引器声明中的 set 访问器上指定的特性,或者可适用于该访问器关联的方法,或者适用于它的独立的隐式参数。如果不存在 attribute-target-specifier,则此特性适用于方法。如果存在 method attribute-target-specifier,则表明此特性适用于该方法;如果存在 param attribute-target-specifier,则表明此特性适用于该参数;而如果存在 return attribute-target-specifier,则表明此特性适用于该返回值。
  • 在事件声明的添加或移除访问器声明上指定的特性,或者适用于该访问器关联的方法,或者适用于它的独立参数。如果不存在 attribute-target-specifier,则此特性适用于方法。如果存在 method attribute-target-specifier,则表明此特性适用于该方法;如果存在 param attribute-target-specifier,则表明此特性适用于该参数;而如果存在 return attribute-target-specifier,则表明此特性适用于该返回值。

在其他上下文中,允许包含一个 attribute-target-specifier,但这样做是没有必要的。例如,类声明既可以包括也可以省略说明符 type:

[type:
Author("Brian Kernighan")]
class Class1 {}

[Author("Dennis
Ritchie")]
class Class2 {}

如果指定了无效的 attribute-target-specifier,则会发生错误。例如,不能将说明符 param 用在类声明中:

[param:
Author("Brian Kernighan")]      //
Error
class Class1 {}

按照约定,特性类的名称均带有 Attribute 后缀。type-name 形式的 attribute-name既可以包含也可以省略此后缀。如果发现特性类中同时出现带和不带此后缀的名称,则引用时就可能出现多义性,从而导致运行时错误。如果在拼写 attribute-name 时,明确说明其最右边的 identifier 为逐字标识符(第 2.4.2 节),则它仅匹配没有后缀的特性,从而能够解决这类多义性。下面的示例

using System;

[AttributeUsage(AttributeTargets.All)]
public class X: Attribute
{}

[AttributeUsage(AttributeTargets.All)]
public class XAttribute: Attribute
{}

[X]                   // Error: ambiguity
class Class1 {}

[XAttribute]          // Refers to XAttribute
class Class2 {}

[@X]                  // Refers to X
class Class3 {}

[@XAttribute]         // Refers to XAttribute
class Class4 {}

演示两个分别名为 X 和 XAttribute 的特性类。特性 [X] 具有多义性,因为该特性既可引用 X 也可引用 XAttribute。使用逐字标识符能够在这种极少见的情况下表明确切的意图。特性 [XAttribute] 是明确的(尽管当存在名为 XAttributeAttribute 的特性类时,该特性将是不明确的!)。如果移除了类 X 的声明,那么上述两个特性都将引用名为 XAttribute 的特性类,如下所示:

using
System;

[AttributeUsage(AttributeTargets.All)]
public class XAttribute: Attribute
{}

[X]                   // Refers to XAttribute
class Class1 {}

[XAttribute]          // Refers to XAttribute
class Class2 {}

[@X]                  // Error: no attribute named
"X"
class Class3 {}

在同一个实体中多次使用单次使用的特性类属于编译时错误。下面的示例

using
System;

[AttributeUsage(AttributeTargets.Class)]
public class HelpStringAttribute: Attribute
{
string value;

public HelpStringAttribute(string value) {
     this.value = value;
}

public string Value {
     get {...}
}
}

[HelpString("Description
of Class1")]
[HelpString("Another description of Class1")]
public class Class1 {}

产生编译时错误,因为它尝试在 Class1 的声明中多次使用单次使用的特性类 HelpString。

如果表达式 E 满足下列所有条件,则该表达式为 attribute-argument-expression:

  • E 的类型是特性参数类型(第 17.1.3 节)。
  • 在编译时,E 的值可以解析为下列之一:
  • 常量值。
  • 一个 System.Type 对象。
  • attribute-argument-expression 的一维数组。

例如:

using
System;

[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute: Attribute
{
public int P1 {
     get {...}
     set {...}
}

public Type P2 {
     get {...}
     set {...}
}

public object P3 {
     get {...}
     set {...}
}
}

[Test(P1 =
1234, P3 = new int[] {1, 3, 5}, P2 = typeof(float))]
class MyClass {}

用作特性实参表达式的 typeof-expression(第 7.6.11 节)可引用非泛型类型、封闭构造类型或未绑定的泛型类型,但是不能引用开放类型。这用于确保在编译时可以解析表达式。

class A:
Attribute
{
public A(Type t) {...}
}

class
G<T>
{
[A(typeof(T))] T t;                // Error, open type in attribute
}

class X
{
[A(typeof(List<int>))] int x;      // Ok, closed constructed type
[A(typeof(List<>))] int y;         // Ok, unbound generic type
}

1.3 特性实例

特性实例
(attribute instance) 是一个实例,用于在运行时表示特性。特性是用特性类、定位参数和命名参数定义的。特性实例是一个特性类的实例,它是用定位参数和命名参数初始化后得到的。

特性实例的检索涉及编译时和运行时处理,详见后面几节中的介绍。

1.3.1 特性的编译

对于一个具有特性类 T、positional-argument-list P 和 named-argument-list N 的 attribute 的编译过程由下列步骤组成:

  • 遵循形式为
    new T(P)的 object-creation-expression 的编译规则所规定的步骤进行编译时处理。这些步骤或者导致编译时错误,或者确定 T 上的可以在运行时调用的实例构造函数 C。
  • 如果 C 不具有公共可访问性,则发生编译时错误。
  • 对于 N 中的每个 named-argument Arg:
  • 将 Name 设为 identifier(属于 named-argument Arg)。
  • Name 必须标识 T 中的一个非静态读写字段或属性。如果 T 没有这样的字段或属性,则发生编译时错误。
  • 保留以下信息用于特性的运行时实例化:特性类 T、T 上的实例构造函数 C、positional-argument-list P 和 named-argument-list N。

1.3.2 特性实例的运行时检索

对一个 attribute 进行编译后,会产生一个特性类 T、一个 T 上的实例构造函数 C、一个 positional-argument-list P 和一个 named-argument-list N。给定了上述信息后,就可以在运行时使用下列步骤进行检索来生成一个特性实例:

  • 遵循执行
    new T(P) 形式的
    object-creation-expression(使用在编译时确定的实例构造函数 C)的运行时处理步骤。这些步骤或者导致异常,或者产生 T 的一个实例 O。
  • 对于 N 中的每个 named-argument Arg,按以下顺序进行处理:
  • 将 Name 设为 identifier(属于 named-argument Arg)。如果 Name 未在 O 上标识一个非静态公共读写字段或属性,则将引发异常。
  • 将 Value 设为 attribute-argument-expression(属于 Arg)的计算结果。
  • 如果 Name 标识 O 上的一个字段,则将此字段设置为 Value。
  • 否则,Name 将标识 O 上的一个属性。将此属性设置为 Value。
  • 结果为
    O,它是已经用 positional-argument-list P 和 named-argument-list N 初始化了的特性类 T 的一个实例。

1.4 保留特性

少数特性以某种方式影响语言。这些特性包括:

  • System.AttributeUsageAttribute(第 17.4.1 节),它用于描述可以以哪些方式使用特性类。
  • System.Diagnostics.ConditionalAttribute(第 17.4.2 节),用于定义条件方法。
  • System.ObsoleteAttribute(第 17.4.3 节),用于将某个成员标记为已过时。
  • System.Runtime.CompilerServices.CallerLineNumberAttribute、System.Runtime.CompilerServices.CallerFilePathAttribute 和 System.Runtime.CompilerServices.CallerMemberNameAttribute(第 17.4.4 节),用于提供有关可选参数的调用上下文的信息。

1.4.1 AttributeUsage 特性

AttributeUsage 特性用于描述使用特性类的方式。

使用 AttributeUsage 特性修饰的类必须直接或间接从 System.Attribute 派生。其他情况下,将发生编译时错误。

namespace
System
{
[AttributeUsage(AttributeTargets.Class)]
public class AttributeUsageAttribute:
Attribute
{
     public
AttributeUsageAttribute(AttributeTargets validOn) {...}

public virtual bool AllowMultiple { get
{...} set {...} }

public virtual bool Inherited { get {...}
set {...} }

public virtual AttributeTargets ValidOn {
get {...} }
}

public enum AttributeTargets
{
     Assembly   = 0x0001,
     Module        = 0x0002,
     Class      = 0x0004,
     Struct        = 0x0008,
     Enum          =
0x0010,
     Constructor = 0x0020,
     Method        = 0x0040,
     Property   = 0x0080,
     Field      = 0x0100,
     Event      = 0x0200,
     Interface = 0x0400,
     Parameter = 0x0800,
     Delegate   = 0x1000,
     ReturnValue = 0x2000,

All = Assembly | Module | Class | Struct |
Enum | Constructor |
        Method | Property | Field | Event
| Interface | Parameter |
        Delegate | ReturnValue
}
}

1.4.2 Conditional 特性

通过特性 Conditional \b  可实现条件方法和条件特性类的定义。

namespace System.Diagnostics
{
[AttributeUsage(AttributeTargets.Method |
AttributeTargets.Class,
                   AllowMultiple = true)]
public class ConditionalAttribute:
Attribute
{
     public ConditionalAttribute(string
conditionString) {...}

public
string ConditionString { get {...} }
}
}

1.4.2.1 条件方法

用 Conditional 特性修饰的方法是条件方法。Conditional 特性通过测试条件编译符号来指示条件。当运行到一个条件方法调用时,是否执行该调用,要根据出现该调用时是否已定义了此符号来确定。如果定义了此符号,则执行该调用;否则省略该调用(包括对调用的接收器和形参的计算)。

条件方法要受到以下限制:

  • 条件方法必须是 class-declaration 或 struct-declaration 中的方法。如果在接口声明中的方法上指定 Conditional 特性,将出现编译时错误。
  • 条件方法必须具有 void 返回类型。
  • 不能用 override 修饰符标记条件方法。但是,可以用 virtual 修饰符标记条件方法。此类方法的重写方法隐含为有条件的方法,而且不能用 Conditional 特性显式标记。
  • 条件方法不能是接口方法的实现。其他情况下,将发生编译时错误。

此外,如果条件方法用在 delegate-creation-expression 中,也会发生编译时错误。下面的示例

#define DEBUG

using System;
using System.Diagnostics;

class Class1
{
[Conditional("DEBUG")]
public static void M() {
     Console.WriteLine("Executed
Class1.M");
}
}

class Class2
{
public static void Test() {
     Class1.M();
}
}

将 Class1.M 声明为条件方法。Class2 的 Test 方法将调用此方法。由于定义了条件编译符号 DEBUG,因此如果调用 Class2.Test,则它会调用 M。如果尚未定义符号 DEBUG,则 Class2.Test 将不会调用 Class1.M。

一定要注意包含或排除对条件方法的调用是由该调用所在处的条件编译符号控制的。在下面的示例中

文件 class1.cs:

using System.Diagnostics;

class Class1
{
[Conditional("DEBUG")]
public static void F() {
     Console.WriteLine("Executed
Class1.F");
}
}

文件 class2.cs:

#define DEBUG

class Class2
{
public static void G() {
     Class1.F();              // F is called
}
}

文件 class3.cs:

#undef DEBUG

class Class3
{
public static void H() {
     Class1.F();              // F is not called
}
}

类 Class2 和 Class3 均包含对条件方法 Class1.F 的调用,根据是否定义了 DEBUG,此调用是有条件的。由于在 Class2 的上下文中定义了此符号而在 Class3 的上下文中没有定义,因此在 Class2 中包含了对 F 的调用,而在 Class3 中省略了对 F 的调用。

在继承链中使用条件方法可能引起混乱。通过 base.M 形式的 base 对条件方法进行的调用受正常条件方法调用规则的限制。在下面的示例中

文件 class1.cs:

using
System;
using System.Diagnostics;

class Class1

{
[Conditional("DEBUG")]
public virtual void M() {
     Console.WriteLine("Class1.M
executed");
}
}

文件 class2.cs:

using
System;

class
Class2: Class1
{
public override void M() {
     Console.WriteLine("Class2.M
executed");
     base.M();                   // base.M is not called!
}
}

文件 class3.cs:

#define
DEBUG

using
System;

class Class3
{
public static void Test() {
     Class2 c = new Class2();
     c.M();                      // M is called
}
}

Class2 包括一个对在其基类中定义的 M 的调用。此调用被省略,因为基方法是条件性的,依赖于符号 DEBUG 是否存在,而该符号在此处没有定义。因此,该方法仅向控制台写入“Class2.M executed”。审慎使用 pp-declaration 可以消除这类问题。

1.4.2.2 条件特性类

使用一个或多个 Conditional 特性修饰的特性类(第 17.1 节)就是条件特性类 (conditional attribute class)。条件特性类因此与在其 Conditional 特性中声明的条件编译符号关联。本示例:

using
System;
using System.Diagnostics;
[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}

将 TestAttribute 声明为与条件编译符号 ALPHA 和 BETA 关联的条件特性类。

如果在特性说明处定义了一个或多个关联的条件编译符号,则条件特性的特性说明(第 17.2 节)也会包括在内;否则会忽略特性说明。

注意包含或排除条件特性类的特性规范是由该指定所在位置的条件编译符号控制的,这一点很重要。在下面的示例中

文件 test.cs:

using
System;
using System.Diagnostics;

[Conditional("DEBUG")]

public class
TestAttribute : Attribute {}

文件 class1.cs:

#define
DEBUG

[Test]             // TestAttribute is specified

class Class1
{}

文件 class2.cs:

#undef DEBUG

[Test]             // TestAttribute is not specified

class Class2
{}

类 Class1 和 Class2 均使用特性 Test 进行修饰,根据是否定义了 DEBUG,此特性是有条件的。由于此符号是在 Class1 而不是 Class2 的上下文中定义的,因此包含 Class1 中 Test 特性的规范,而省略 Class2 中 Test 特性的规范。

1.4.3 Obsolete 特性

Obsolete 特性用于标记不应该再使用的类型和类型成员。

namespace System
{
[AttributeUsage(
     AttributeTargets.Class |
     AttributeTargets.Struct |
    AttributeTargets.Enum |
     AttributeTargets.Interface |
     AttributeTargets.Delegate |
     AttributeTargets.Method |
     AttributeTargets.Constructor |
     AttributeTargets.Property |
     AttributeTargets.Field |
     AttributeTargets.Event,
     Inherited = false)
]
public class ObsoleteAttribute: Attribute
{
     public ObsoleteAttribute() {...}

public
ObsoleteAttribute(string message) {...}

public
ObsoleteAttribute(string message, bool error) {...}

public
string Message { get {...} }

public
bool IsError { get {...} }
}
}

如果程序使用了由 Obsolete 特性修饰的类型或成员,则编译器将发出警告或错误信息。具体而言,如果没有提供错误参数,或者如果提供了错误参数但该错误参数的值为 false,则编译器将发出警告。如果指定了错误参数并且该错误参数的值为 true,则会引发一个编译时错误。

在下面的示例中

[Obsolete("This class is obsolete; use
class B instead")]
class A
{
public void F() {}
}

class B
{
public void F() {}
}

class Test
{
static void Main() {
     A a = new A();       // Warning
     a.F();
}
}

类 A 是用 Obsolete 特性修饰的。Main 的代码中,每次使用 A 时均会导致一个包含指定消息“This class is obsolete; use class B instead”(此类已过时;请改用类 B)的警告。

1.4.4 调用方信息特性

出于日志记录和报告等目的,使用函数成员获取某些有关调用代码的编译时信息有时是很有用的。调用方信息特性提供了透明地传递此类信息的方法。

当使用调用方信息特性之一批注了可选参数时,在调用中省略相应参数不一定导致替换为默认参数值。相反,如果有关调用上下文的指定信息可用,则该信息将作为参数值进行传递。

例如:

using System.Runtime.CompilerServices

public void Log(
[CallerLineNumber] int line = -1,
[CallerFilePath]   string path = null,
[CallerMemberName] string name = null
)
{
Console.WriteLine((line < 0) ?
"No line" : "Line "+ line);
Console.WriteLine((path == null) ?
"No file path" : path);
Console.WriteLine((name == null) ?
"No member name" : name);
}

调用不带参数的 Log() 时将输出调用的行号和文件路径,以及在其中发生调用的成员的名称。

调用方信息特性可以出现在任何位置的可选参数上,包括出现在委托声明中。但是,特定调用方信息特性对于它们可以规定其特性的参数类型有限制,因此将始终存在从替换值到参数类型的隐式转换。

在分部方法声明的定义部分和实现部分的参数上出现相同的调用方信息特性会导致出错。仅应用定义部分的调用方信息特性,而忽略仅出现在实现部分的调用方信息特性。

调用方信息不会影响重载决策。当仍从调用方的源代码中省略特性化的可选参数时,重载决策将以忽略其他省略的可选参数(第 7.5.3 节)的相同方式忽略这些参数。

只有在源代码中显式调用函数时,才会替换为调用方信息。隐式父构造函数调用等隐式调用没有源位置,因此不会替换为调用方信息。此外,动态绑定的调用将不会替换为调用方信息。在这种情况下省略调用方信息特性化参数时,将改为使用指定的参数默认值。

查询表达式是一个例外。这些被视为是句法扩展,并且如果用户将调用扩展为使用调用方信息特性而省略可选参数,则将替换为调用方信息。所用位置是从中生成调用的查询子句的位置。

如果在给定的参数中指定了多个调用方信息特性,则它们的优先顺序如下:CallerLineNumber、CallerFilePath、CallerMemberName。

1.4.4.1 CallerLineNumber 特性

当存在从常量值 int.MaxValue 到参数类型的标准隐式转换(第 6.3.1 节)时,允许对可选参数使用 System.Runtime.CompilerServices.CallerLineNumberAttribute。这将确保可以传递任何小于等于该值的非负行号,而不会出错。

如果从源代码中的某个位置调用函数时使用 CallerLineNumberAttribute 而省略了可选形参,则表示该位置的行号的数字文本(而不是默认参数值)将用作此调用的实参。

如果该调用跨多行,则选择哪一行将取决于具体实现。

请注意,行号可能会受 #line 指令(第 2.5.7 节)影响。

1.4.4.2 CallerFilePath 特性

当存在从 string 到参数类型的标准隐式转换(第 6.3.1 节)时,允许对可选参数使用 System.Runtime.CompilerServices.CallerFilePathAttribute。

如果从源代码中的某个位置调用函数时使用 CallerFilePathAttribute 而省略了可选形参,则表示该位置的文件路径的字符串文本(而不是默认参数值)将用作此调用的实参。

文件路径的格式取决于具体实现。

请注意,文件路径可能会受 #line 指令(第 2.5.7 节)影响。

1.4.4.3 CallerMemberName 特性

当存在从 string 到参数类型的标准隐式转换(第 6.3.1 节)时,允许对可选参数使用 System.Runtime.CompilerServices.CallerMemberNameAttribute。

如果从源代码中的函数成员体内或应用于函数成员本身、函数成员返回类型、形参或类型形参的某个特性中的某个位置调用函数时使用 CallerMemberNameAttribute 而省略可选形参,则表示该成员名称的字符串文本(而不是默认参数值)将用作此调用的实参。

对于发生在泛型方法内的调用,仅使用方法名称本身,而不带类型形参列表。

对于发生在显式接口成员实现中的调用,仅使用方法名称本身,而不带前面的接口限定。

对于发生在属性访问器或事件访问器中的调用,所使用的成员名称是该属性或事件本身的名称。

对于发生在索引器访问器中的调用,所使用的成员名称是由索引器成员(如果存在)的 IndexerNameAttribute(第 17.5.2.1 节)提供的名称或默认名称 Item(如果索引器成员不存在)。

对于发生在实例构造函数、静态构造函数、析构函数和运算符的声明中的调用,所使用的成员名称取决于具体实现。

1.5 互操作的特性

注意:本节仅适用于 C# Microsoft .NET 实现。

1.5.1 与 COM 和 Win32 组件的互操作

.NET 运行时提供大量特性,通过这些特性,C# 程序可以与使用 COM 和 Win32 DLL 编写的组件进行交互操作。例如,可以在 static extern 方法上使用 DllImport 特性来表示该方法的实现应该到 Win32 DLL 中去查找。这些特性可在 System.Runtime.InteropServices 命名空间中找到,有关这些特性的详细文档可在 .NET 运行时文档中找到。

1.5.2 与其他 .NET 语言的互操作

1.5.2.1 IndexerName 特性

索引器是利用索引属性在 .NET 中实现的,并且具有一个属于 .NET 元数据的名称。如果索引器没有被指定 IndexerName 特性,则默认情况下将使用名称 Item。IndexerName 特性使开发人员可以重写此默认名称并指定不同的名称。

namespace
System.Runtime.CompilerServices.CSharp
{
[AttributeUsage(AttributeTargets.Property)]
public class IndexerNameAttribute:
Attribute
{
     public IndexerNameAttribute(string
indexerName) {...}

public
string Value { get {...} }
       }
}