是错误的,因为它试图在Class1的声明中多次使用HelpString,而HelpString是一个单次属性类。
如果所有下面的语句都为真,表达式E就是一个属性参数表达式:
· E的类型是一个属性参数类型 (§17.1.3)。
· 在编译时,E的数值可以被分解为下面的一个:
· 一个常数数据。
· 一个System.Type 对象。
· 一个属性参数表达式的一维数组。
1.3 属性实例
一个属性实例是一个在运行时代表一个属性的实例。一个属性用一个属性、位置参数和名称参数定义。一个属性实例是一个属性类的实例,它用位置和名称参数来初始化。
属性实例的搜索涉及到所有编译时和运行时过程,就像在下面的章节中所描述的一样。
1.3.1 一个属性的编译
一个有属性类T,位置参数列表P和名称参数列表N的属性的编译过程包括下面几步:
· 对形式为new T(P)的对象创建表达式进行编译,是按照编译时过程步骤进行。这些步骤或是会产生一个编译时错误,或是确定一个可以在运行时调用的T的构造函数。把这个构造函数称为C。
· 如果上面步骤中决定的构造函数没有公共的可访问性,就会发生一个编译时错误。
· 对每个N中的名称参数Arg:
· 把Name 作为名称参数Arg的标识符。
· Name 必须标识一个T中的非静态读写公共域或属性。如果T没有那样的域或属性,就会发生一个编译时错误。
· 为属性实例的运行时实例化保持下面的信息:属性类T,T中的构造函数C,位置参数列表P和名称参数列表N。
1.3.2 一个属性实例的运行时检索
对属性的编译产生了一个属性类T,T的构造函数C,位置参数列表P和名称参数列表N。给出这些信息,一个属性实例就可以在运行时按照下面的步骤进行检索:
· 为执行一个形式为T(P)的对象创建表达式,跟随运行时过程步骤,在编译时确定构造函数C的使用。这些步骤或导致一个异常,或产生一个实例T,把这个实例称作O。
· 对于每个N中的名称参数Arg in N,以下面的顺序:
· 让Name 作为名称参数Arg 的标识符。如果Name 没有在O中指定一个非静态公共读写域或属性,那么会抛出一个异常。
· 让 Value 作为对Arg 属性参数表达式求值得结果。
· 如果 Name 确定了一个O中的域,那么把这个域设置为数据Value。
· 否则, Name 确定一个O中的属性。把这个属性设置为数据Value。
- 结果是O,一个属性类T的实例,它被初始化为有位置参数列表P和名称参数列表N。
1.4 保留的属性
属性的一小部分在某些方面影响语言。这些属性包括:
· System.AttributeUsageAttribute, 它被用来描述一个可以使用属性类的方法。
· System.ConditionalAttribute, 它被用来定义条件方法。
· System.ObsoleteAttribute, 它被用来把一个成员标注为废弃的。
1.4.1 AttributeUsage 属性
AttributeUsage 属性被用来描述一种属性类可以被使用的方式。
一个用AttributeUsage 属性声明的类必须从System.Attribute派生,或者直接或者间接。否则,会产生一个编译时错误。
[AttributeUsage(AttributeTargets.Class)]
public class AttributeUsageAttribute: System.Attribute
{
public AttributeUsageAttribute(AttributeTargets validOn) {…}
public AttributeUsageAttribute(AttributeTargets validOn,
bool allowMultiple,
bool inherited) {…}
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,
All = Assembly | Module | Class | Struct | Enum | Constructor |
Method | Property | Field | Event | Interface | Parameter |
Delegate,
ClassMembers = Class | Struct | Enum | Constructor | Method |
Property | Field | Event | Delegate | Interface,
}
1.4.2 条件属性
条件属性使得条件方法的定义变为可能。条件属性在一个预处理标识符的形式中指定一个条件。对于一个条件方法的调用或者被包括或者被忽略,要根据在调用的地方这个符号是否被定义来决定。如果符号被定义了,那么方法调用被包括,如果符号没有定义,那么调用被忽略。
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class ConditionalAttribute: System.Attribute
{
public ConditionalAttribute(string conditionalSymbol) {…}
public string ConditionalSymbol { get {…} }
}
一个条件方法要受下面的约束:
· 条件方法必须是一个类声明中的方法。如果条件属性在接口方法中被指定,那么就会发生一个编译时错误。
· 条件方法必须返回一个void类型。
· 条件方法不能用override修饰符来标注。一个条件方法可以用virtual修饰符来标注。覆盖这样的一个方法是隐含地有条件的,并且不能用条件属性来显式地标注。
· 条件方法不能是一个接口方法的实现程序,否则,会产生编译时错误。
而且,如果条件方法被用在代表创建表达式中,也会产生一个编译时错误。例子
#define DEBUG
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。
注意,下面这点很重要,包含或去掉一个对条件方法的调用,是被在调用的地方的预定义标识符所控制的。在例子中
// Begin class1.cs
class Class1
{
[Conditional("DEBUG")]
public static void F() {
Console.WriteLine("Executed Class1.F");
}
}
// End class1.cs
// Begin class2.cs
#define DEBUG
class Class2
{
public static void G {
Class1.F(); // F is called
}
}
// End class2.cs
// Begin class3.cs
#undef DEBUG
class Class3
{
public static void H {
Class1.F(); // F is not called
}
}
// End class3.cs
类Class2 和 Class3每个都包含对条件方法Class1.F的调用,这个条件是基于DEBUG是存在的还是不存在地。由于这个符号在Class2 的上下文中被定义了,但是没有在Class3中定义,因此对Class2中的F的调用被实际进行,而对Class3中的F的调用被忽略了。
在一个继承链中使用条件方法可能会很混乱。通过base对条件方法的调用,形式为base.M ,要受通常条件方法调用规则的限制。在例子中
// Begin class1.cs
class Class1
{
[Conditional("DEBUG")]
public virtual void M() {
Console.WriteLine("Class1.M executed");
}
}
// End class1.cs
// Begin class2.cs
class Class2: Class1
{
public override void M() {
Console.WriteLine("Class2.M executed");
base.M(); // base.M is not called!
}
}
// End class2.cs
// Begin class3.cs
#define DEBUG
class Class3
{
public static void Test() {
Class
c.M(); // M is called
}
}
// End class3.cs
Class2 包含一个在它的基类中定义的对M的调用。因为基本方法是根据符号DEBUG的存在来确定,而它没有被定义,所以这个调用就被忽略了。因此,写到控制台的方法只是"Class2.M executed"。聪明地使用pp-declarations 就可以解决这样的问题。
1.4.3 废弃的属性
废弃的属性被用于对不再使用的程序元素进行标记。
[AttributeUsage(AttributeTargets.All)]
public class ObsoleteAttribute: System.Attribute
{
public ObsoleteAttribute(string message) {…}
public string Message { get {…} }
public bool IsError{ get {…} set {…} }
}
评论