C#允许开发人员以特性(attribute)的形式为程序添加说明性信息。特性定义了与类、结构、方法等相关的附加信息(元数据)。例如,可以定义一个特性来制定该类中将要现实的按钮的类型。特性包含在所应用想之前的方括号“[]”中,因此它不是类得成员,而仅仅是该项的附加信息。
1. 特性基础
特性由System.Attribute的派生类来支持。因此,所有的特性类必须是Attribute类得子类。尽管Attribute类定义了大量的功能,但是用特性时一般不会用到全部的功能。按照惯例,特性类名通常是用后缀Attribute。例如,ErrorAttribute是一个描述错误的特性类的名称。
在声明特性类时,前面应加上AttributeUsage特性。这个内置特性指明了该特性适用的项的类型。例如,把特性限定为进攻方法适用。
2. 创建特性
在特性类中,可以定义支持该特性的成员。特性类通常非常简单,只包含少量的字段或属性(property)。例如,特性可以定义一个符号,用于描述应用此特性的项。这种特性的声明如下所示:
[AttributeUsage(AttributeTargets.All)]
public class RemberAttribute : Attribute
{
string pri_remark;
public RemberAttribute(string comment)
{
pri_remark = comment;
}
public string Remark
{
get{return pri_remark;}
}
}
该类的名称为 RemarkAttribute,类得声明语句之前有 AttributeUsage特性,它指明RemarkAttribute能够应用于所有类型的项。通过使用AttributeUsage ,可以限制应用特性的项类型。
接下来,声明RemarkAttribute,它继承Attribute类。在RemberAttribute中只有一个私有字段pri_remark,用于支持公共的只读属性 Remark 。 此属性用于保存与该特性相关的描述。RemarkAttribute只有一个公共的构造函数,该构造函数带有一个字符串参数。参数被赋值给Remark.
3. 连接特性
一旦定义了特性类,就可以把它连接到项上。可以把特性放置在应用它的项之前,并且通过把它的构造函数包含在方括号中来指示。
[Rember("this class uses an attribute.")]
public class UseAttrib {
//...
}
这里只使用了名称Remark。尽管这种方式是正确的,但在连接特性时使用全称会更加安全,因为这样可以避免可能产生的混乱和歧义。
4.获取对象的特性
一旦吧特性连接到项上,程序的其他部分就能获取该特性。为了获取特性,通常需要使用下面两个方法之一。
第一个方法是GetCustomAttributes(),它由MemberInfo类定义并且被Type类继承。此方法可以获取连接到某个项的所有特性的列表。
它的基本形式为: object[] GetCustomAttribute(bool searchBases)
如果searchBases为真,那么继承链中所有基类的特性都会被包含进来。否则,就只获取指定类型本身定义的特性。
第二个方法是由Attribute定义的GetCustomAttribute(),它的一种形式为:
static Attribute GetCustomAttribute(MembetInifo mi,Type attribtype)
其中,mi是一个MemberInfo类型的对象,它描述了要获得那个项的特性。希望获得的特性由Attribtype指定。如果一直想要获得的特性的名称,可以使用此方法。
[AttributeUsage(AttributeTargets.All)]
public class RemarkAttribute : Attribute
{
string pri_remark;
public RemarkAttribute(string comment)
{
pri_remark = comment;
}
public string Remark
{
get { return pri_remark; }
}
}
[Remark("this class uses an attribute.")]
public class UseAttrib
{
}
class NTest
{
static void Main(string[] args)
{
Type t = typeof(UseAttrib);
Console.Write("Attributes in " + t.Name + ": ");
object[] attribs = t.GetCustomAttributes(false);
foreach (object o in attribs)
{
Console.WriteLine(o);
}
Console.Write("Remark:");
Type tRemAtt=typeof(RemarkAttribute);
RemarkAttribute ra=(RemarkAttribute) Attribute.GetCustomAttribute(t,tRemAtt);
Console.WriteLine(ra.Remark);
Console.Read();
}
}
位置参数和命名参数
当调用特性的构造函数时,可以使用两种方式的参数,一种为位置参数,另一种为命名参数 。
位置参数与普通的方法调用方式一样,命名参数则为:
[Remark("this class uses an attribute.",name="str")]
命名参数由公共字段或属性来支持,它们必须可读写并且是非静态的。
在使用的时候,首先定义位置参数(如果存在的话),然后是每一个命名参数的赋值语句。命名参数的顺序并不重要,它们的初始值也不必给出。为给定初始值时,参数将使用默认值。
三个内置特性
C#定义了多个内置特性,其中最重要的3个是AttributeUsage、Conditioinal和Obsolete。这些特性在很多场合都会用到。
1. AttributeUsage特性
AttributeUsage特性指定了能够应用该特性的项的类型。AttributeUsage是System.AttributeUsageAttribute类得别名,它的构造函数为:
AttributeUsage(AttributeTargets item)
AttributeUsage枚举定义表:如下
All | Assembly | Class | Constructor |
Delegate | Enum | Event | Field |
GenericParameter | Interface | Method | Module |
Parameter | Property | ReturnValue | Struct |
可以将这些值中的两个或多个执行OR运算。例如,为了指定一个智能应用于字段和属性的特性,则使用: AttributeTargets.Field | AttributeTargets.Property
AttribyteUsage支持两个命名参数。第一个是AllowMultiple,它是一个bool值。如果该值为真,那么这个特性就能够多次应用于单个项。第二个是Inherited,它也是一个bool值。如果钙质为真,那么该特性可以被派生类继承,否则就不能被继承。一般地,AllowMultiple的默认值为假,而Inherited的默认值为真。
AttribyteUsage还指定了一个只读属性ValidOn,该属性返回一个AttributeTargets类型的值来说明可以应用该特性的项的类型。其默认值为AttributeTargets.All。
2.Conditional特性
Conditional或许是C#中最有趣的内置特性。它允许开发人员创建条件方法(conditional methods).条件方法只有在通过#define定义了特定的符号时才能被调用。否则,方法将被忽略。因此,条件方法可以取代使用#if条件编译命令。
Conditional是System.Diagnostics.ConditionalAttribute的别名。要使用Conditional特性,必须先包含System.Diagnostics名称空间。
#define TRIAL
using System;
using System.Reflection;
using System.Diagnostics;
namespace work
{
class Test
{
[Conditional("TRIAL")]
public void Trial()
{
Console.WriteLine("Trial version,nont for distributton.");
}
[Conditional("RELEASE")]
public void Release()
{
Console.WriteLine("Final release version.");
}
static void Main(string[] args)
{
Test test = new Test();
test.Trial();
test.Release();
Console.Read();
}
}
}
程序定义了符号TRIAL。Trial()和Release()方法。这两个方法都使用了Conditional特性,其基本形式为:
[Conditional(symbol)]
其中,symbol是决定是否执行该方法的符号。主意这个特性只能用于方法。如果symbol已经被定义,方法在被调用时就会执行。如果未定义此符号,则不执行相应的方法。
条件方法也有一些限制:它们必须返回void ,必须是类或结构的成员而不能使接口,以及不能使用override关键组织作为前缀。
3. Obsolete特性
Obsolete特性时System.ObsoleteAttribute的简写形式,它可以把程序中的元素标记为废弃的。
其基本形式为:
[Obsolete("message")]
编译该程序元素时将显示message。下面给出一个简短实例:
class Test
{
[Obsolete("Use MyMeth2,instead.")]
public static int MyMeth(int a, int b)
{
return a / b;
}
public static int MyMeth2(int a, int b)
{
return b == 0 ? 0 : a / b;
}
static void Main(string[] args)
{
Console.WriteLine("4 / 3 is " + Test.MyMeth(4, 3));
Console.WriteLine("4 / 3 is " + Test.MyMeth2(4, 3));
Console.Read();
}
}
编译此程序时,如果在Main()中遇到对MyMeth()的调用,那么将产生一个警告,建议用户使用MyMethod2()方法。
Obsolete的第二种形式如下:
[Obsolete("messsage",error)]
其中,error是一个布尔值。如果它为真,使用废弃项时,将产生编译错误而不是警告。错误与警告的区别在于,包含错误的程序不能被编译成可执行程序。