17.C#基础之特性(完成)

     C#语言的一个重要特征就是使程序员能够为程序中定义的各种实体附加一些说明信息,比如类中方法的可访问性就是通过可访问修饰符来指定的;C#使程序员可以创造说明性信息的新的种类,这就是特性;创造的特性可以附加到各种程序实体,而且在运行时环境中还可以检索这些特性信息。比如:一个框架可以定义一个名为HelpAttribute的特性,该特性可以放在某些程序元素上,以提供从这些程序元素到其文档说明的映射。

     特性是通过特性类的声明定义的,特性类可以具有定位和命名参数。特性是使用特性规范附加到C#程序中的实体上,而且可以在运行时作为特性实例检索。

  17.1特性类

     从抽象类System.Attribute派生的类,不论直接的还是间接的,都称为特性类。

     关于特性类的声明定义一种新特性,它可以被放置在其他声明上。按照约定,特性类的名称带有Attribute后缀,使用特性时可以省略此后缀。

   17.1.1 特性用法

     AttributeUsage特性:用于描述使用特性类的方式。AttributeUsage具有一个定位参数,该参数使特性类能够指定自己可以用在哪种声明上。

17.C#基础之特性(完成)

     上面定义了一个名为SimpleAttribute的特性类,此特性类只能放在类声明和接口声明上,正如上面说的,后缀是可以省略的。比如:

17.C#基础之特性(完成)

     AttributeUsage还具有一个名为AllowMultiple的命名参数:用于说明对于某个给定实体,是否可以多次使用该特性。若AllowMultiple为true,则此特性类是多次性特性类,可以在一个实体上多次被应用;若是false或未指定,则表示是一次性特性类,在一个实体上最多使用一次。比如:

17.C#基础之特性(完成)

17.C#基础之特性(完成)

     上面先定义了一个名为AuthorAttribute的多次性特性类,然后显示了一个使用两次AuthorAttribute特性的类声明。

     此外AttributeUsage另一个名为Inherited的命名参数:指示在基类上指定该特性时,该特性是否会被派生类继承。若Inherited为true,则表示会被继承,反之false或未指定,则不会继承。

   17.1.2 定位和命名参数

     刚才写到,特性类可以具有定位参数和命名参数:

                     一个有效的定位参数序列是由特性类的每个公共实例构造函数为该特性类定义的;

                     一个命名参数是由特性类的每个非静态公共读写字段和特性为该特性定义的。

17.C#基础之特性(完成)

     上面定义了一个名为HelpAttribute的特性类,它具有一个定位参数(url)和一个命名参数(Topic)。虽然Url特性是非静态的和公共的,但它不是读写!此特性就可以这样来用:

17.C#基础之特性(完成)

   17.1.3 特性参数类型

     特性类的定位参数和命名参数的类型仅限于特性参数类型。它们是:

17.C#基础之特性(完成)

  17.2特性专用化

     特性专用化:就是将以前定义的特性应用到某个声明上。

     特性本身是一段附加说明性信息,所以可以把它指定给某个声明,也可以在全局范围指定特性,即在包含程序集或模块上指定特性。

     特性是在特性节中指定的。特性节由一对方括号组成,方括号括着一个用逗号分隔的、含有一个或多个特性的列表。特性的顺序等细节并不重要。

17.C#基础之特性(完成)

17.C#基础之特性(完成)

17.C#基础之特性(完成)

     如上所述,特性由一个特姓名和一个可选的定位和命名参数列表组成。如果有定位参数,则放在命名参数前面;定位参数包含一个特性参数表达式,命名参数包含一个名称,后跟一个等号和一个特性参数表达式。

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

         *在全局范围指定的特性可以应用于目标程序及或目标模块,系统没有为此上下文提供默认形式,所以始终需要一个特性目标说明符。若存在assembly特性目标说明符,则表示此特性适用于指定的目标程序集;若是module,则表示适用于指定的目标模块;

         *在委托声明上指定的特性,不是适用于所声明的委托,就是适用于它的返回值。如果不存在特性目标说明符或是type特性目标说明符,表示适用于该委托;若是return,表示使用返回值;

         *在方法声明上指定的特性,同理,如果不存在或是method特性目标说明符,表示适用于该方法;若是return,表示适用于返回值;

         *在运算符声明上指定的特性,同理,如果不存在或是method特性目标说明符,表示适用于该运算符;若是return,表示使用返回值;

         *对于在省略了事件访问器的事件声明上指定的特性,它的目标对象由三种可能的选择:所声明的事件、与该事件关联的字段(如果该事件是非抽象事件)、与该事件关联的add和remove方法。如果不存在或是event特性目标说明符,则此特性适用于该事件;如果是field,表示该特性适用于该字段;如果是method,则表示适用于这些方法;

         *在特性或索引器中的get访问器声明上指定特性,如果不存在或是method特性目标说明符,则表示此特性适用于该方法;如果是return,表示适用于该返回值;

         *在属性或索引器中的set访问器上指定的特性,如果不存在或是method特性目标说明符,则表示此特性适用于该方法;如果是param,表示此特性适用于该参数;

         *在事件声明的添加或移除访问器声明上指定的特性,如果不存在或是method特性目标说明符,则表示此特性适用于该方法;如果是param,则表示此特性适用于该参数。

     如果指定了无效的特性目标说明符,则会发生错误。比如:

17.C#基础之特性(完成)

     param不能用于类声明中。

     按约定,特性类名称均带Attribute后缀,且类型名形式的特性名可以省略。但要注意下面这个情况,会出现多义性。比如:

17.C#基础之特性(完成)

     如果表达式E符合下面所有条件,则表达式E为"特性参数表达式":

17.C#基础之特性(完成)

     例如:

17.C#基础之特性(完成)

  17.3特性实例

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

   17.3.1 特性的编译

     对于一个具有特性类T、定位参数列表P和命名参数列表N的特性的编译过程由下列步骤组成:

          *遵循形式为new T(P)的对象创建表达式编译规则所规定的步骤进行编译时处理。这些步骤道么导致编译时错误,要么确定T上的可以在运行时调用的实例构造函数C;

          *如果C布局偶公共可访问性,则会发生编译时错误;

          *对于N中的每个命名参数Arg,将Name设为命名参数Arg的标识符;Name必须标识T中的一个非静态读写public字段或特性。如果T没有这样的字段或特性,则会发生编译时错误;

          *保存下列信息:特性类T、T上的实例构造函数C、定位参数列表P和命名参数列表N,以供在运行时实例化该特性时调用。

   17.3.2 特性实例的运行时检索

     对于一个特性进行编译后,会产生一个特性类T、一个T上的实例构造函数C、一个定位参数列表P和一个命名参数列表N。给定这些信息后,就可以在运行时使用下列步骤进行检索来生成一个特性实例:

          *遵循执行new T(P)的对象创建表达式(使用在编译时确定的实例构造函数C)的运行时处理步骤。不折步骤要么导致异常,要么产生T的一个实例O;

          *对于N中的每个命名参数Arg的标识符,按一下顺序进行处理:

                 1.将Name设为命名参数Arg的标识符。如果Name标识的不是P上的一个非静态读写public字段或特性,则引发异常;

                 2.将Value设为Arg的特性参数表达式的计算结构;

                 3.如果Name标识O上的一个字段,则将此字段赋值为Value;否则Name就标识O的一个特性,将此特性赋值为Value;

                 4.结果为O,它是已经用定位参数列表P和命名参数列表N初始化了的特性类T的一个实例。

  17.4保留特性

     少数特性以某种方式影响语言。这些特性包括:System.AttributeUsageAttribute、System.Diagnostics.ConditionalAttribute、System.ObsoleteAttribute。

   17.4.1 AttributeUsage特性

     AttributeUsage特性:用于描述使用特性类的方式。用AttributeUsage特性修饰的类必须直接或间接从System.Attribute派生。

17.C#基础之特性(完成)

   17.4.2 Conditional特性

     Conditional特性:用于定义条件方法。当运行到一个条件方法调用时,是否执行该调用,要根据出现该调用时是否已定义了此符号来确定;如果定义了,就执行,如果没有就省略该调用。

17.C#基础之特性(完成)

     条件方法要受到限制:*方法必须时类声明或结构声明中的方法,如果时再接口声明中的方法上指、接口方法或委托表达式,都会发生编译时错误;

                         *条件方法必须是void返回类型;

                         *不能用override修饰符标识条件方法,但可以用virtual修饰符标识条件方法。此类方法的重写方法隐含为有条件的方法,而且不能用Conditional特性显式标识。

17.C#基础之特性(完成)

     Class1.M为条件方法,Class2.Test调用此方法。由于定义了条件编译符号是DEBUG,所以Class2.Test可以调用Class1.M。

     一定要注意包含或排除对条件方法的调用是由该调用所在的条件编译符号控制的。比如:

17.C#基础之特性(完成)

     其中上面Class2定义了DEBUG,Class3没有定义,所以Class3不会调用F。

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

17.C#基础之特性(完成)

     其中Class2没有定义符号,所以不会被调用;但可以用PP声明消除这类问题。

   17.4.3 Obsolete特性

     Obsolete特性:它用于将某个成员标记为已过时,即标记不应该再使用的类型和类型成员。

17.C#基础之特性(完成)

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

17.C#基础之特性(完成)

     类A是Obsolete特性修饰的。在Main代码中,每次使用A都会导致一个包含指定信息"This class is obsolete;use class B instead"的警告。

  17.5互操作的特性

   17.5.1与COM和Win32组件的互操作

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

   17.5.2 与其他.NET语言的交互操作

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

17.C#基础之特性(完成)