24.2.4 常用特性

1.CLSCompliantAttribute特性

公共语言规范(Common Language Specification,CLS)定义了程序集在跨编程语言使用时必须符合的命名限制、数据类型和规则。BCL中提供的CLSCompliantAttribute特性可以指示程序元素是否符合CLS,如果不符合可以通过警告信息进行提示。如果有兼容CLS规范的要求,可以要求所有程序集使用CLSCompliantAttribute特性以显式指示和CLS的兼容性。如果程序集不使用该特性,将无法很快地判断程序集是否符合CLS规范。对于CLS规范遵从性的概念仅对程序集、模块、类型和类型成员有意义,对成员签名的各部分没有意义。因此,CLSCompliantAttribute在应用于参数或返回值时将被直接忽略。

下面的示例将CLSCompliantAttribute应用到整个程序集。这是一个叫做MyLibrary的程序集,它是一个dll,被ProgrammingCSharp4.exe程序集所使用,如图24-15所示。

24.2.4 常用特性 - 图1

图 24-15 MyLibrary和ProgrammingCSharp4的关系

可见,ProgrammingCSharp4引用了MyLibrary,我们要在MyLibrary的BaseClass类中添加CLSCompliantAttribute特性,以指示MyLibrary程序集是否符合CLS规范,如代码清单24-21所示。

代码清单24-21 使用CLSCompliantAttribute特性指示程序集符合CLS规范

24.2.4 常用特性 - 图2


public void setvalue(UInt32 value)

{

}

}

}


该特性除了可以放在类的声明里以外,还可以放在AssemblyInfo.cs中,它的位置如图24-16所示。

24.2.4 常用特性 - 图3

图 24-16 AssemblyInfo.cs的位置

修改后的代码如下所示:


using System;

using System.Reflection;

using System.Runtime.InteropServices;

//有关程序集的常规信息通过以下

//特性集控制。更改这些特性值可修改

//与程序集关联的信息。

[assembly:AssemblyTitle("MyLibrary")]

[assembly:AssemblyDescription(“")]

[assembly:AssemblyConfiguration(“")]

[assembly:AssemblyCompany(“")]

[assembly:AssemblyProduct("MyLibrary")]

[assembly:AssemblyCopyright("Copyright©2010")]

[assembly:AssemblyTrademark(“")]

[assembly:AssemblyCulture(“")]

[assembly:CLSCompliant(true)]

//将ComVisible设置为false使此程序集中的类型

//对COM组件不可见。如果需要从COM访问此程序集中的类型,

//则将该类型上的ComVisible特性设置为true。

[assembly:ComVisible(false)]

//如果此项目向COM公开,则下列GUID用于类型库的ID

[assembly:Guid("26499acd-20ac-475a-9fbb-f498f70caf3b")]

//程序集的版本信息由下面四个值组成:

//

//主版本

//次版本

//内部版本号

//修订号

//

//可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值,

//方法是按如下所示使用"*":

//[assembly:AssemblyVersion("1.0.*")]

[assembly:AssemblyVersion("1.0.0.0")]

[assembly:AssemblyFileVersion("1.0.0.0")]


不管采用上述哪种方式,在编译MyLibrary才想起的时候,CLR会产生如下编译警告。


警告1 参数类型"uint"不符合CLS

C:\WorkPlace\ProgrammingCSharp4\MyLibrary\BaseClass.cs

13 30 MyLibrary

警告3 参数类型"uint"不符合CLS

C:\WorkPlace\ProgrammingCSharp4\MyLibrary\BaseClass.cs

18 30 MyLibrary

警告2 仅大小写不同的标识符"MyLibrary.BaseClass.setvalue(uint)"不符合CLS

C:\WorkPlace\ProgrammingCSharp4\MyLibrary\BaseClass.cs

18 21 MyLibrary


其中,警告1和警告3是一类,指的是uint类型不满足CLS兼容性要求。警告2自己是一类,指的是由于两个成员方法之间,仅有大小写不同这一区别,也不满足CLS兼容性要求,主要是针对VB.NET等对大小写不敏感的语言。

警告2比较好理解,我们研究一下为什么会有警告1和警告3。UInt32的实现如下所示:


[Serializable,StructLayout(LayoutKind.Sequential),CLSCompliant(false),ComVisible(true)]

public struct UInt32:IComparable,IFormattable,IConvertible,

IComparable<uint>,IEquatable<uint>

{

//……

}


可以看出UInt32类型应用了CLSCompliant特性,并且指示不符合CLS兼容性要求。到这里,大家应该知道原因了。

上面提到,仅在一定条件下编译才会有上述警告信息,具体的条件包括:

❑不兼容代码所在的程序集应用了CLSCompliantAttribute特性,并指示该程序集应该符合CLS兼容性要求;

❑不兼容代码所在的程序集被其他程序集引用,如果没有被引用是不会发出上述警告信息的。图24-17演示了上述各种条件。

24.2.4 常用特性 - 图4

图 24-17 不兼容性警告产生的条件

2.ObsoleteAttribute特性

ObsoleteAttribute特性将某个程序元素标记为不建议再继续使用,换句话说就是该程序元素“已过时,不再推荐使用”。一般情况下,会有它的替代元素可供使用。每次使用这种标记为已过时的元素时,编译器将会生成警告或错误信息,这取决于传递给特性的第二个参数的值。

ObsoleteAttribute特性类的签名如代码清单24-22所示,从签名可知,该特性不能应用在如下程序元素上:Assembly(程序集)、Parameter(参数)、Module(模块)、ReturnValue(返回值)、GenericParameter(泛型参数)。

另外,该特性也不支持由派生类继承。

代码清单24-22 ObsoleteAttribute特性类的签名


[Serializable,ComVisible(true),AttributeUsage(AttributeTargets.Delegate|

AttributeTargets.Interface|AttributeTargets.Event|AttributeTargets.Field|

AttributeTargets.Property|AttributeTargets.Method|

AttributeTargets.Constructor|AttributeTargets.Enum|AttributeTargets.Struct|

AttributeTargets.Class,Inherited=false)]

public sealed class ObsoleteAttribute:Attribute

{

//……

}


ObsoleteAttribute共有3个公共构造函数,这意味着有3种使用该特性的方式。这些构造函数是:

❑public ObsoleteAttribute()

❑public ObsoleteAttribute(string message)

❑public ObsoleteAttribute(string message,bool error)

可见,使用该特性的3种方式是:

❑不使用参数,仅仅标记某程序元素为“已过时”,并产生一条警告信息;

❑使用一个参数,是一个附加的说明字符串,该字符串将出现在警告消息中,可以给该元素的使用者更加明确的信息,例如可以改用其他什么类型,等等;

❑使用两个参数,第一个参数同上,第二个参数是一个布尔值,当值为true时编译器将产生一条错误信息,提示该元素过期,而不是警告信息;为false时则仍为警告信息。如果是错误信息,就意味着必须修复以后程序才能继续运行,警告则可以忽略,不影响程序的正常运行。

接下来,我们分别使用这三种形式来演示ObsoleteAttribute特性的应用,如代码清单24-23所示。

代码清单24-23 ObsoleteAttribute特性示例


[Obsolete]

public class FirstClass

{

[Obsolete(“Method1()方法已过期,请查询开发手册获取更多信息。”)]

public void Method1(){}

[Obsolete(“Method2()方法已过期,请查询开发手册获取更多信息。”,true)]

public void Method2(){}

}

public class MyProgram

{

public static void Main()

{

FirstClass first=new FirstClass();

first.Method1();

first.Method2();

}

}


以上代码编译后VisualStudio会产生出如下警告/错误信息,如图24-18所示。

24.2.4 常用特性 - 图5

图 24-18 VisualStudio产生的警告/错误信息

3.ConditionalAttribute特性

ConditionalAttribute特性可以根据条件符号执行方法。该特性具有一个公共构造函数和一个公共只读的属性ConditionString,其构造函数如下:


public ConditionalAttribute(string conditionString);


因此该特性只具有一个定位参数,而没有命名参数。接下来,我们继续看一下该特性类的签名,如代码清单24-24所示。

代码清单24-24 ConditionalAttribute特性的签名


[Serializable,AttributeUsage(AttributeTargets.Method|AttributeTargets.Class,

AllowMultiple=true),ComVisible(true)]

public sealed class ConditionalAttribute:Attribute

{

//……

}


可见,该特性可以应用的程序元素为:

❑方法:必须为在类或结果中声明的方法,且不能有返回值。

❑类:可以应用到类。

除此之外,不能应用在其他任何程序元素之上。另外,我们还注意到,该特性允许在同一程序元素上应用多次。

例如,给一个方法应用了ConditionalAttribute特性,如代码清单24-25所示,

代码清单24-25 给Method1()方法应用ConditionalAttribute特性


public class FirstClass

{

[Conditional("SIGNAL_ON")]

public void Method1()

{

System.Console.WriteLine("Hello from Method1()");

}

public void Method2(){}

}


在上述代码中,ConditionalAttribute特性的定位参数的值为"SIGNAL_ON",该值为条件符号,如果没有定义SIGNAL_ON条件符号,则对于该特性所应用的方法就不会被调用。可以对比如下两段程序,首先是定义了SIGNAL_ON条件符号的情况,如代码清单24-26所示。

代码清单24-26 定义了SIGNAL_ON条件符号的情况


define SIGNAL_ON

using System.Diagnostics;

namespace ProgrammingCSharp4

{

class AttributeSample

{

public static void Main()

{

FirstClass first=new FirstClass();

first.Method1();

first.Method2();

}

}

public class FirstClass

{

[Conditional("SIGNAL_ON")]

public void Method1()

{

System.Console.WriteLine("Hello from Method1()");

}

public void Method2(){}

}

}


由于定义了SIGNAL_ON条件符号,因此对于first.Method1()的调用得以执行,运行结果如下:


Hello from Method1()

请按任意键继续……


那么,不定义SIGNAL_ON条件符号的情况会怎样呢?具体如代码清单24-27所示。

代码清单24-27 不定义SIGNAL_ON条件符号的情况


using System.Diagnostics;

namespace ProgrammingCSharp4

{

class AttributeSample

{

public static void Main()

{

FirstClass first=new FirstClass();

first.Method1();

first.Method2();

}

}

public class FirstClass

{

[Conditional("SIGNAL_ON")]

public void Method1()

{

System.Console.WriteLine("Hello from Method1()");

}

public void Method2(){}

}

}


因为SIGNAL_ON条件符号不存在,那么对first.Method1()的调用将被忽略,上述代码运行的结果也印证了我们的判断,如下:


请按任意键继续……


我们在前文提到过,ConditionalAttribute特性允许在同一程序元素上应用多次,如果在一个方法上使用两个ConditionalAttribute特性,这两个特性之间的关系实际上是以OR运算符连接在一起的。换句话说,只要定义了其中任何一个条件符号,结果都将包含对该方法的调用。

代码清单24-28 使用多个ConditionalAttribute特性


define SIGNAL_2_ON

using System.Diagnostics;

namespace ProgrammingCSharp4

{

class AttributeSample

{

public static void Main()

{

FirstClass first=new FirstClass();

first.Method1();

first.Method2();

}

}

public class FirstClass

{

[Conditional("SIGNAL_ON"),Conditional("SIGNAL_2_ON")]

public void Method1()

{

System.Console.WriteLine("Hello from Method1()");

}

public void Method2(){}

}

}


上述代码中的Method1()方法应用了两个ConditionalAttribute特性,分别对应SIGNAL_ON和SIGNAL_2_ON这两个条件符号。我们预定义了SIGNAL_2_ON符号,满足了特性的条件,因此对于Method1()方法的调用得以执行,运行结果如下:


Hello from Method1()

请按任意键继续……


有些应用场景可能要求两个特性定义的条件符号同时存在,才包含对某个方法的调用,并没有现成的特性可供使用,但可以迂回实现,如代码清单24-29所示。只有当SIGNAL_ON和SIGNAL_2_ON两个条件符号同时定义了,才会包含对于Method1()的调用。

代码清单24-29 当两个条件符号同时定义时才包含对方法的调用


define SIGNAL_ON

define SIGNAL_2_ON

using System.Diagnostics;

namespace ProgrammingCSharp4

{

class AttributeSample

{

public static void Main()

{

FirstClass first=new FirstClass();

first.Method1();

}

}

public class FirstClass

{

[Conditional("SIGNAL_ON")]

public void Method1()

{

Method2();

}

[Conditional("SIGNAL_2_ON")]

public void Method2(){

System.Console.WriteLine("Hello from Method2()");

}

}

}


上述代码的运行结果如下:


Hello from Method2()

请按任意键继续……


最后,要补充说明的是,ConditionalAttribute特性也可以应用在我们的自定义特性中,那么应用的结果就是仅当指定的条件符号定义的时候,自定义特性才会生效,简单看一下例子,比较容易理解,如代码清单24-30所示。

代码清单24-30 在自定义特性中应用ConditionalAttribute特性


define SIGNAL_ON

using System;

using System.Diagnostics;

using System.Reflection;

namespace ProgrammingCSharp4

{

class AttributeSample

{

public static void Main()

{

FirstClass first=new FirstClass();

Type type=first.GetType();

MethodInfo method=type.GetMethod("Method1");

if(null!=method)

{

object[]attrs=method.GetCustomAttributes(false);

foreach(object attr in attrs)

{

if(attr is LogEnableAttribute)

{

Console.WriteLine("LogEnableAttribute found.");

}

}

}

}

}

public class FirstClass

{

[LogEnable(IsEnable=true)]

public void Method1()

{

}

}

[Conditional("SIGNAL_ON")]

[AttributeUsage(AttributeTargets.All,AllowMultiple=false,Inherited=true)]

public class LogEnableAttribute:Attribute

{

public LogEnableAttribute()

{

Console.WriteLine(“LogEnableAttribute初始化”);

}

public bool IsEnable

{

get;

set;

}

}

}


这里我们定义了条件符号SIGNAL_ON,运行结果为:


LogEnableAttribute初始化

LogEnableAttribute found.

请按任意键继续……

如果去掉条件符号的定义,则运行结果为:

请按任意键继续……