C#6.0语言规范(十七) 特性
许多C#语言使程序员能够指定有关程序中定义的实体的声明性信息。例如,在一个类中的方法的可访问性由与装饰它指定method_modifier小号public
,protected
,internal
,和private
。
C#使程序员能够发明新的声明性信息,称为特性。然后,程序员可以将特性附加到各种程序实体,并在运行时环境中检索特性信息。例如,框架可以定义HelpAttribute
可以放置在某些程序元素(例如类和方法)上的特性,以提供从这些程序元素到其文档的映射。
特性是通过特性类(特性类)的声明定义的,特性类可以具有位置和命名参数(位置和命名参数)。特性使用特性规范(特性规范)附加到C#程序中的实体,并且可以在运行时作为特性实例(特性实例)进行检索。
特性类
从抽象类派生的类System.Attribute
,无论是直接还是间接,都是特性类。特性类的声明定义了一种可以放在声明上的新特性。按照惯例,特性类的后缀为Attribute
。特性的使用可以包括或省略该后缀。
特性用法
特性AttributeUsage
(The AttributeUsage特性)用于描述如何使用特性类。
AttributeUsage
有一个位置参数(位置和命名参数),它使特性类能够指定可以使用它的声明种类。这个例子
1 using System; 2 3 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] 4 public class SimpleAttribute: Attribute 5 { 6 ... 7 }
定义一个名为的特性类SimpleAttribute
,只能放在class_declaration和interface_declaration上。这个例子
1 [Simple] class Class1 {...} 2 3 [Simple] interface Interface1 {...}
显示了Simple
特性的几种用法。尽管使用名称定义了SimpleAttribute
此特性,但在使用此特性时,Attribute
可能会省略后缀,从而生成短名称Simple
。因此,上面的示例在语义上等效于以下内容:
1 [SimpleAttribute] class Class1 {...} 2 3 [SimpleAttribute] interface Interface1 {...}
AttributeUsage
有一个名为的参数(位置和命名参数)AllowMultiple
,它指示是否可以为给定实体多次指定该特性。如果AllowMultiple
特性类为true,则该特性类是多用途特性类,并且可以在实体上多次指定。如果AllowMultiple
特性类为false或未指定,则该特性类是一次性特性类,并且可以在实体上最多指定一次。
这个例子
1 using System; 2 3 [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 4 public class AuthorAttribute: Attribute 5 { 6 private string name; 7 8 public AuthorAttribute(string name) { 9 this.name = name; 10 } 11 12 public string Name { 13 get { return name; } 14 } 15 }
定义一个名为的多用途特性类AuthorAttribute
。这个例子
1 [Author("Brian Kernighan"), Author("Dennis Ritchie")] 2 class Class1 3 { 4 ... 5 }
显示了一个具有两个Author
特性用途的类声明。
AttributeUsage
还有另一个名为的参数Inherited
,它指示在基类上指定的特性是否也由从该基类派生的类继承。如果Inherited
特性类为true,则继承该特性。如果Inherited
特性类为false,则不继承该特性。如果未指定,则其默认值为true。
X
没有AttributeUsage
附加特性的特性类,如
1 using System; 2 3 class X: Attribute {...}
相当于以下内容:
1 using System; 2 3 [AttributeUsage( 4 AttributeTargets.All, 5 AllowMultiple = false, 6 Inherited = true) 7 ] 8 class X: Attribute {...}
位置和命名参数
特性类可以具有位置参数和命名参数。特性类的每个公共实例构造函数定义该特性类的有效位置参数序列。特性类的每个非静态公共读写字段和特性都定义特性类的命名参数。
这个例子
1 using System; 2 3 [AttributeUsage(AttributeTargets.Class)] 4 public class HelpAttribute: Attribute 5 { 6 public HelpAttribute(string url) { // Positional parameter 7 ... 8 } 9 10 public string Topic { // Named parameter 11 get {...} 12 set {...} 13 } 14 15 public string Url { 16 get {...} 17 } 18 }
定义一个名为的特性类HelpAttribute
,它具有一个位置参数url
和一个命名参数Topic
。虽然它是非静态和公共的,但该特性Url
不定义命名参数,因为它不是读写的。
此特性类可能如下使用:
1 [Help("http://www.mycompany.com/.../Class1.htm")] 2 class Class1 3 { 4 ... 5 } 6 7 [Help("http://www.mycompany.com/.../Misc.htm", Topic = "Class2")] 8 class Class2 9 { 10 ... 11 }
特性参数类型
特性类的位置和命名参数类型仅限于特性参数类型,它们是:
- 其中以下类型:
bool
,byte
,char
,double
,float
,int
,long
,sbyte
,short
,string
,uint
,ulong
,ushort
。 - 类型
object
。 - 类型
System.Type
。 - 枚举类型,前提是它具有公共可访问性,并且嵌套类型(如果有)也具有公共可访问性(特性规范)。
- 上述类型的一维阵列。
- 不具有这些类型之一的构造函数参数或公共字段不能用作特性规范中的位置参数或命名参数。
特性规范
特性规范是将先前定义的特性应用于声明。特性是为声明指定的一条附加声明性信息。可以在全局范围(指定包含的程序集或模块上的特性)和 type_declaration(类型声明),class_member_declaration(类型参数约束), interface_member_declaration(接口成员),struct_member_declaration( Struct成员), enum_member_declaration指定特性 s(枚举成员), accessor_declarations(访问者),event_accessor_declarations(类字段事件)和formal_parameter_list(方法参数)。
特性在特性部分中指定。特性部分由一对方括号组成,它们围绕一个或多个特性的逗号分隔列表。在这样的列表中指定特性的顺序以及附加到同一程序实体的部分的顺序并不重要。例如,特性规格[A][B]
,[B][A]
,[A,B]
,和[B,A]
是等价的。
1 global_attributes 2 : global_attribute_section+ 3 ; 4 5 global_attribute_section 6 : '[' global_attribute_target_specifier attribute_list ']' 7 | '[' global_attribute_target_specifier attribute_list ',' ']' 8 ; 9 10 global_attribute_target_specifier 11 : global_attribute_target ':' 12 ; 13 14 global_attribute_target 15 : 'assembly' 16 | 'module' 17 ; 18 19 attributes 20 : attribute_section+ 21 ; 22 23 attribute_section 24 : '[' attribute_target_specifier? attribute_list ']' 25 | '[' attribute_target_specifier? attribute_list ',' ']' 26 ; 27 28 attribute_target_specifier 29 : attribute_target ':' 30 ; 31 32 attribute_target 33 : 'field' 34 | 'event' 35 | 'method' 36 | 'param' 37 | 'property' 38 | 'return' 39 | 'type' 40 ; 41 42 attribute_list 43 : attribute (',' attribute)* 44 ; 45 46 attribute 47 : attribute_name attribute_arguments? 48 ; 49 50 attribute_name 51 : type_name 52 ; 53 54 attribute_arguments 55 : '(' positional_argument_list? ')' 56 | '(' positional_argument_list ',' named_argument_list ')' 57 | '(' named_argument_list ')' 58 ; 59 60 positional_argument_list 61 : positional_argument (',' positional_argument)* 62 ; 63 64 positional_argument 65 : attribute_argument_expression 66 ; 67 68 named_argument_list 69 : named_argument (',' named_argument)* 70 ; 71 72 named_argument 73 : identifier '=' attribute_argument_expression 74 ; 75 76 attribute_argument_expression 77 : expression 78 ;
特性由attribute_name和可选的位置和命名参数列表组成。位置参数(如果有)位于命名参数之前。位置参数由attribute_argument_expression组成; 命名参数由一个名称后跟一个等号组成,后跟一个attribute_argument_expression,它们一起受到与简单赋值相同的规则的约束。命名参数的顺序并不重要。
所述特性名称标识一个特性类。如果attribute_name的形式为type_name,则此名称必须引用特性类。否则,发生编译时错误。这个例子
1 class Class1 {} 2 3 [Class1] class Class2 {} // Error
某些上下文允许在多个目标上指定特性。程序可以通过包含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的存在表明该特性适用于返回值。 - 在省略事件访问器的事件声明上指定的特性可以应用于正在声明的事件,关联字段(如果事件不是抽象),或者应用于关联的添加和删除方法。如果没有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的存在表明该特性适用于返回值。 - 在事件声明的add或remove访问器声明中指定的特性可以应用于关联的方法或其单独的参数。如果没有attribute_target_specifier,则该特性将应用于该方法。
method
attribute_target_specifier的存在表明该特性适用于该方法;param
attribute_target_specifier的存在表明该特性适用于该参数;return
attribute_target_specifier的存在表明该特性适用于返回值。
在其他上下文中,允许包含attribute_target_specifier但不必要。例如,类声明可以包含或省略说明符type
:
1 [type: Author("Brian Kernighan")] 2 class Class1 {} 3 4 [Author("Dennis Ritchie")] 5 class Class2 {}
指定无效的attribute_target_specifier是错误的。例如,说明符param
不能用于类声明:
1 [param: Author("Brian Kernighan")] // Error 2 class Class1 {}
按照惯例,特性类的后缀为Attribute
。type_name形式的attribute_name可以包含或省略此后缀。如果找到带有和不带此后缀的特性类,则会出现歧义,并导致编译时错误。如果拼写attribute_name使得其最右侧标识符是逐字标识符(标识符),则仅匹配没有后缀的特性,从而使得能够解决这种歧义。这个例子
1 using System; 2 3 [AttributeUsage(AttributeTargets.All)] 4 public class X: Attribute 5 {} 6 7 [AttributeUsage(AttributeTargets.All)] 8 public class XAttribute: Attribute 9 {} 10 11 [X] // Error: ambiguity 12 class Class1 {} 13 14 [XAttribute] // Refers to XAttribute 15 class Class2 {} 16 17 [@X] // Refers to X 18 class Class3 {} 19 20 [@XAttribute] // Refers to XAttribute 21 class Class4 {}
显示两个名为X
和的特性类XAttribute
。特性[X]
是不明确的,因为它可以引用任何一个X
或XAttribute
。使用逐字标识符可以在极少数情况下指定确切的意图。该特性[XAttribute]
不含糊不清(尽管有一个名为XAttributeAttribute
!的特性类)。如果X
删除了类的声明,则两个特性都引用名为的特性类XAttribute
,如下所示:
1 using System; 2 3 [AttributeUsage(AttributeTargets.All)] 4 public class XAttribute: Attribute 5 {} 6 7 [X] // Refers to XAttribute 8 class Class1 {} 9 10 [XAttribute] // Refers to XAttribute 11 class Class2 {} 12 13 [@X] // Error: no attribute named "X" 14 class Class3 {}
在同一实体上多次使用一次性特性类是编译时错误。这个例子
1 using System; 2 3 [AttributeUsage(AttributeTargets.Class)] 4 public class HelpStringAttribute: Attribute 5 { 6 string value; 7 8 public HelpStringAttribute(string value) { 9 this.value = value; 10 } 11 12 public string Value { 13 get {...} 14 } 15 } 16 17 [HelpString("Description of Class1")] 18 [HelpString("Another description of Class1")] 19 public class Class1 {}
导致编译时错误,因为它尝试HelpString
在声明时多次使用,这是一次性特性类Class1
。
如果以下所有语句都为真,则表达式E
是attribute_argument_expression:
- 类型
E
是特性参数类型(特性参数类型)。 - 在编译时,
E
可以将值解析为以下之一:- 一个恒定的值。
- 一个
System.Type
对象。 - attribute_argument_expression的一维数组。
例如:
1 using System; 2 3 [AttributeUsage(AttributeTargets.Class)] 4 public class TestAttribute: Attribute 5 { 6 public int P1 { 7 get {...} 8 set {...} 9 } 10 11 public Type P2 { 12 get {...} 13 set {...} 14 } 15 16 public object P3 { 17 get {...} 18 set {...} 19 } 20 } 21 22 [Test(P1 = 1234, P3 = new int[] {1, 3, 5}, P2 = typeof(float))] 23 class MyClass {}
用作特性参数表达式的typeof_expression(typeof运算符)可以引用非泛型类型,闭合构造类型或未绑定泛型类型,但它不能引用开放类型。这是为了确保表达式可以在编译时解析。
1 class A: Attribute 2 { 3 public A(Type t) {...} 4 } 5 6 class G<T> 7 { 8 [A(typeof(T))] T t; // Error, open type in attribute 9 } 10 11 class X 12 { 13 [A(typeof(List<int>))] int x; // Ok, closed constructed type 14 [A(typeof(List<>))] int y; // Ok, unbound generic type 15 }
特性实例
一个特性实例是表示在运行时的特性的实例。使用特性类,位置参数和命名参数定义特性。特性实例是使用位置参数和命名参数初始化的特性类的实例。
检索特性实例涉及编译时和运行时处理,如以下各节所述。
编译特性
使用特性class ,positional_argument_list和named_argument_list编译特性包括以下步骤:T
P
N
- 按照编译时处理步骤编译表单的object_creation_expression
new T(P)
。这些步骤或者导致编译时错误,或确定实例构造C
上T
,可以在运行时被调用。 - 如果
C
没有公共可访问性,则发生编译时错误。 - 对于每个named_argument
Arg
在N
:- 让我们
Name
为标识的的named_argumentArg
。 Name
必须标识非静态读写公共字段或特性T
。如果T
没有这样的字段或特性,则发生编译时错误。
- 让我们
- 保存好特性的运行时实例的以下信息:特性类
T
的实例构造C
上T
,该positional_argument_listP
和named_argument_listN
。
运行时检索特性实例
一个编译特性产生一个特性类T
,实例构造C
上T
,一个positional_argument_list P
和named_argument_list N
。根据此信息,可以使用以下步骤在运行时检索特性实例:
- 遵循运行时处理步骤,使用在编译时确定的实例构造函数来执行表单的object_creation_expression。这些步骤或者导致异常,或者产生一个实例的。
new T(P)
C
O
T
- 对于每个named_argument
Arg
中N
,依次是:- 让我们
Name
为标识的的named_argumentArg
。如果Name
未标识非静态公共读写字段或特性O
,则抛出异常。 - 让我们
Value
评估一下attribute_argument_expression的结果Arg
。 - 如果
Name
标识了一个字段O
,则将该字段设置为Value
。 - 否则,
Name
标识一个特性O
。将此特性设置为Value
。 - 结果是
O
,T
使用positional_argument_listP
和named_argument_list 初始化的特性类的实例N
。
- 让我们
保留特性
少数特性以某种方式影响语言。这些特性包括:
System.AttributeUsageAttribute
(AttributeUsage特性),用于描述可以使用特性类的方式。System.Diagnostics.ConditionalAttribute
(Conditional特性),用于定义条件方法。System.ObsoleteAttribute
(The Obsolete特性),用于将成员标记为过时。System.Runtime.CompilerServices.CallerLineNumberAttribute
,System.Runtime.CompilerServices.CallerFilePathAttribute
和System.Runtime.CompilerServices.CallerMemberNameAttribute
(呼叫者信息的特性),其用于提供关于调用上下文到可选参数的信息。
AttributeUsage特性
该特性AttributeUsage
用于描述可以使用特性类的方式。
使用该AttributeUsage
特性修饰的类必须System.Attribute
直接或间接派生。否则,发生编译时错误。
1 namespace System 2 { 3 [AttributeUsage(AttributeTargets.Class)] 4 public class AttributeUsageAttribute: Attribute 5 { 6 public AttributeUsageAttribute(AttributeTargets validOn) {...} 7 public virtual bool AllowMultiple { get {...} set {...} } 8 public virtual bool Inherited { get {...} set {...} } 9 public virtual AttributeTargets ValidOn { get {...} } 10 } 11 12 public enum AttributeTargets 13 { 14 Assembly = 0x0001, 15 Module = 0x0002, 16 Class = 0x0004, 17 Struct = 0x0008, 18 Enum = 0x0010, 19 Constructor = 0x0020, 20 Method = 0x0040, 21 Property = 0x0080, 22 Field = 0x0100, 23 Event = 0x0200, 24 Interface = 0x0400, 25 Parameter = 0x0800, 26 Delegate = 0x1000, 27 ReturnValue = 0x2000, 28 29 All = Assembly | Module | Class | Struct | Enum | Constructor | 30 Method | Property | Field | Event | Interface | Parameter | 31 Delegate | ReturnValue 32 } 33 }
条件特性
该特性Conditional
支持定义条件方法和条件特性类。
1 namespace System.Diagnostics 2 { 3 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] 4 public class ConditionalAttribute: Attribute 5 { 6 public ConditionalAttribute(string conditionString) {...} 7 public string ConditionString { get {...} } 8 } 9 }
条件方法
使用该Conditional
特性修饰的方法是条件方法。该Conditional
特性通过测试条件编译符号来指示条件。根据是否在调用点定义了此符号,可以包含或省略对条件方法的调用。如果定义了符号,则包括呼叫; 否则,省略呼叫(包括接收机的评估和呼叫的参数)。
条件方法受以下限制:
- 条件方法必须是class_declaration或struct_declaration中的方法。如果
Conditional
在接口声明中的方法上指定了特性,则会发生编译时错误。 - 条件方法的返回类型必须为
void
。 - 不能使用
override
修饰符标记条件方法。但是,条件方法可以用virtual
修饰符标记。这种方法的覆盖是隐式条件的,不能用Conditional
特性明确标记。 - 条件方法不能是接口方法的实现。否则,发生编译时错误。
此外,如果在delegate_creation_expression中使用条件方法,则会发生编译时错误。这个例子
1 #define DEBUG 2 3 using System; 4 using System.Diagnostics; 5 6 class Class1 7 { 8 [Conditional("DEBUG")] 9 public static void M() { 10 Console.WriteLine("Executed Class1.M"); 11 } 12 } 13 14 class Class2 15 { 16 public static void Test() { 17 Class1.M(); 18 } 19 }
声明Class1.M
为条件方法。Class2
的Test
方法调用此方法。由于DEBUG
定义了条件编译符号,如果Class2.Test
被调用,它将调用M
。如果符号DEBUG
尚未定义,则Class2.Test
不会调用Class1.M
。
重要的是要注意,包含或排除对条件方法的调用由调用点处的条件编译符号控制。在这个例子中
文件class1.cs
:
1 using System.Diagnostics; 2 3 class Class1 4 { 5 [Conditional("DEBUG")] 6 public static void F() { 7 Console.WriteLine("Executed Class1.F"); 8 } 9 }
文件class2.cs
:
1 #define DEBUG 2 3 class Class2 4 { 5 public static void G() { 6 Class1.F(); // F is called 7 } 8 }
文件class3.cs
:
1 #undef DEBUG 2 3 class Class3 4 { 5 public static void H() { 6 Class1.F(); // F is not called 7 } 8 }
类Class2
和Class3
每个包含对条件方法的调用,条件方法Class1.F
是基于是否DEBUG
定义的条件。因为该符号中的上下文中定义Class2
但不是Class3
,该呼叫F
在Class2
被包括,而呼叫F
中Class3
被省略。
在继承链中使用条件方法可能会令人困惑。通过base
表单base.M
对条件方法进行的调用受普通条件方法调用规则的约束。在这个例子中
文件class1.cs
:
1 using System; 2 using System.Diagnostics; 3 4 class Class1 5 { 6 [Conditional("DEBUG")] 7 public virtual void M() { 8 Console.WriteLine("Class1.M executed"); 9 } 10 }
文件class2.cs
:
1 using System; 2 3 class Class2: Class1 4 { 5 public override void M() { 6 Console.WriteLine("Class2.M executed"); 7 base.M(); // base.M is not called! 8 } 9 }
文件class3.cs
:
1 #define DEBUG 2 3 using System; 4 5 class Class3 6 { 7 public static void Test() { 8 Class2 c = new Class2(); 9 c.M(); // M is called 10 } 11 }
Class2
包括M
对其基类中定义的调用。省略此调用,因为基本方法是基于符号的存在而是条件的DEBUG
,这是未定义的。因此,该方法仅写入控制台“ Class2.M executed
”。明智地使用pp_declaration可以消除这些问题。
条件特性类
用一个或多个特性修饰的特性类(特性类)Conditional
是条件特性类。因此,条件特性类与其Conditional
特性中声明的条件编译符号相关联。这个例子:
1 using System; 2 using System.Diagnostics; 3 [Conditional("ALPHA")] 4 [Conditional("BETA")] 5 public class TestAttribute : Attribute {}
声明TestAttribute
为与条件编译符号相关联的条件特性类ALPHA
和BETA
。
如果在规范点定义了一个或多个相关的条件编译符号,则包括条件特性的特性规范(特性规范),否则省略特性规范。
重要的是要注意,包含或排除条件特性类的特性规范由规范点处的条件编译符号控制。在这个例子中
文件test.cs
:
1 using System; 2 using System.Diagnostics; 3 4 [Conditional("DEBUG")] 5 6 public class TestAttribute : Attribute {}
文件class1.cs
:
1 #define DEBUG 2 3 [Test] // TestAttribute is specified 4 5 class Class1 {}
文件class2.cs
:
1 #undef DEBUG 2 3 [Test] // TestAttribute is not specified 4 5 class Class2 {}
类Class1
和Class2
每个类都使用特性进行修饰Test
,这是基于是否DEBUG
定义的条件。因为该符号中的上下文中定义Class1
但不是Class2
,则的规格Test
上的特性Class1
被包括,所述的说明书,而Test
上特性Class2
被省略。
过时的特性
该特性Obsolete
用于标记不再使用的类型和类型成员。
1 namespace System 2 { 3 [AttributeUsage( 4 AttributeTargets.Class | 5 AttributeTargets.Struct | 6 AttributeTargets.Enum | 7 AttributeTargets.Interface | 8 AttributeTargets.Delegate | 9 AttributeTargets.Method | 10 AttributeTargets.Constructor | 11 AttributeTargets.Property | 12 AttributeTargets.Field | 13 AttributeTargets.Event, 14 Inherited = false) 15 ] 16 public class ObsoleteAttribute: Attribute 17 { 18 public ObsoleteAttribute() {...} 19 public ObsoleteAttribute(string message) {...} 20 public ObsoleteAttribute(string message, bool error) {...} 21 public string Message { get {...} } 22 public bool IsError { get {...} } 23 } 24 }
如果程序使用使用该Obsolete
特性修饰的类型或成员,则编译器会发出警告或错误。具体而言,如果未提供错误参数,或者提供了错误参数且具有值,则编译器会发出警告false
。如果指定了error参数并且具有该值,则编译器会发出错误true
。
在这个例子中
1 [Obsolete("This class is obsolete; use class B instead")] 2 class A 3 { 4 public void F() {} 5 } 6 7 class B 8 { 9 public void F() {} 10 } 11 12 class Test 13 { 14 static void Main() { 15 A a = new A(); // Warning 16 a.F(); 17 } 18 }
该类A
用Obsolete
特性修饰。每次使用的A
在Main
在包括指定的消息,警告结果“这类已废弃;使用B类来代替。”
调用者信息特性
对于诸如日志记录和报告之类的目的,有时函数成员可以获得关于调用代码的某些编译时信息。调用者信息特性提供了透明传递此类信息的方法。
当使用其中一个调用者信息特性注释可选参数时,省略调用中的相应参数不一定会导致替换默认参数值。相反,如果有关调用上下文的指定信息可用,则该信息将作为参数值传递。
例如:
1 using System.Runtime.CompilerServices 2 3 ... 4 5 public void Log( 6 [CallerLineNumber] int line = -1, 7 [CallerFilePath] string path = null, 8 [CallerMemberName] string name = null 9 ) 10 { 11 Console.WriteLine((line < 0) ? "No line" : "Line "+ line); 12 Console.WriteLine((path == null) ? "No file path" : path); 13 Console.WriteLine((name == null) ? "No member name" : name); 14 }
Log()
没有参数的调用将打印调用的行号和文件路径,以及调用发生的成员的名称。
调用者信息特性可以在任何地方的可选参数上发生,包括在委托声明中。但是,特定调用者信息特性对它们可以归属的参数类型有限制,因此始终存在从替换值到参数类型的隐式转换。
在部分方法声明的定义和实现部分的参数上具有相同的调用者信息特性是错误的。仅应用定义部分中的调用者信息特性,而忽略仅在实现部分中出现的调用者信息特性。
来电者信息不会影响重载分辨率。由于仍然从调用者的源代码中省略了特性可选参数,因此重载决策忽略这些参数的方式与忽略其他省略的可选参数(重载分辨率)的方式相同。
只有在源代码中显式调用函数时,才会替换调用者信息。隐式调用(例如隐式父构造函数调用)没有源位置,也不会替换调用者信息。此外,动态绑定的调用不会替换调用者信息。在这种情况下,当省略调用者信息特性参数时,将使用指定的参数默认值。
查询表达式是一个例外。这些被认为是语法扩展,如果它们扩展的调用省略了具有调用者信息特性的可选参数,则将替换调用者信息。使用的位置是生成调用的查询子句的位置。
如果在给定的参数被指定多于一个的呼叫者信息特性,它们是优选按照以下顺序:CallerLineNumber
,CallerFilePath
,CallerMemberName
。
CallerLineNumber特性
在System.Runtime.CompilerServices.CallerLineNumberAttribute
被允许在可选参数时,有一个标准的隐式转换(标准的隐式转换从恒定值)int.MaxValue
的参数的类型。这可确保可以无错误地传递任何达到该值的非负行号。
如果源代码中某个位置的函数调用省略了带有的可选参数CallerLineNumberAttribute
,则表示该位置的行号的数字文字将用作调用的参数,而不是默认参数值。
如果调用跨越多行,则选择的行与实现有关。
请注意,行号可能受#line
指令(行指令)的影响。
CallerFilePath特性
在System.Runtime.CompilerServices.CallerFilePathAttribute
被允许在可选参数时,有一个标准的隐式转换(标准的隐式转换从)string
到参数的类型。
如果源代码中某个位置的函数调用省略了带有的可选参数CallerFilePathAttribute
,则表示该位置文件路径的字符串文字将用作调用的参数而不是默认参数值。
文件路径的格式取决于实现。
请注意,文件路径可能受#line
指令(行指令)的影响。
CallerMemberName特性
在System.Runtime.CompilerServices.CallerMemberNameAttribute
被允许在可选参数时,有一个标准的隐式转换(标准的隐式转换从)string
到参数的类型。
如果从函数成员体内的某个位置或应用于函数成员本身的特性内的函数调用或其源代码中的返回类型,参数或类型参数省略了带有的可选参数CallerMemberNameAttribute
,则表示名称的字符串文字该成员的参数用作调用的参数而不是默认参数值。
对于泛型方法中发生的调用,仅使用方法名称本身,而不使用类型参数列表。
对于在显式接口成员实现中发生的调用,仅使用方法名称本身,而不使用前面的接口限定。
对于特性或事件访问器中发生的调用,使用的成员名称是特性或事件本身的名称。
对于在索引器访问器中发生的调用,使用的成员名称是由索引器成员上的IndexerNameAttribute
(IndexerName特性)提供的成员名称(如果存在),否则为默认名称Item
。
对于在实例构造函数,静态构造函数,析构函数和运算符的声明中发生的调用,使用的成员名称是依赖于实现的。
互操作的特性
注意:本节仅适用于C#的Microsoft .NET实现。
与COM和Win32组件的互操作
.NET运行时提供了大量特性,使C#程序能够与使用COM和Win32 DLL编写的组件进行互操作。例如,DllImport
可以在方法上使用该特性static extern
来指示在Win32 DLL中找到该方法的实现。这些特性可在System.Runtime.InteropServices
命名空间中找到,这些特性的详细文档可在.NET运行时文档中找到。
与其他.NET语言的互操作
IndexerName特性
索引器使用索引特性在.NET中实现,并在.NET元数据中具有名称。如果IndexerName
索引器没有特性,则Item
默认使用该名称。该IndexerName
特性使开发人员可以覆盖此默认值并指定其他名称。
1 namespace System.Runtime.CompilerServices.CSharp 2 { 3 [AttributeUsage(AttributeTargets.Property)] 4 public class IndexerNameAttribute: Attribute 5 { 6 public IndexerNameAttribute(string indexerName) {...} 7 public string Value { get {...} } 8 } 9 }