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-15 MyLibrary和ProgrammingCSharp4的关系
可见,ProgrammingCSharp4引用了MyLibrary,我们要在MyLibrary的BaseClass类中添加CLSCompliantAttribute特性,以指示MyLibrary程序集是否符合CLS规范,如代码清单24-21所示。
代码清单24-21 使用CLSCompliantAttribute特性指示程序集符合CLS规范
public void setvalue(UInt32 value)
{
}
}
}
该特性除了可以放在类的声明里以外,还可以放在AssemblyInfo.cs中,它的位置如图24-16所示。
图 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-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-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.
请按任意键继续……
如果去掉条件符号的定义,则运行结果为:
请按任意键继续……