第十五讲 特征
特征
特征(Attribute)是C#为组件编程引入的一个令人兴奋的创新,它使得我们可以为程序的各种元素如类,结构,接口,方法等提供额外的描述性信息。这些描述性信息在程序代码运行时又可以被我们提取利用。我们先来看一个示例程序:
using System;
public class AuthorAttribute: Attribute //作者特征类
{
public AuthorAttribute(string name) { this.name = name; }
public string Name { get { return name; }}
private string name;
}
[Author("Anders Hejlsberg")]//特征实例化
class MyClass {
int count;
public MyClass(int count) { this.count=count; }
public int Count { get { return count; }
}
}
class Test {
static void Main() {
Type t1= typeof(MyClass);
Type t2=typeof(AuthorAttribute);
object[] arr = t1.GetCustomAttributes(t2, true);//获取特征实例(数组)
Console.Write("The author of MyClass is :");
foreach(object o in arr) {
AuthorAttribute aa = (AuthorAttribute) o;
Console.Write(" {0} ", aa.Name);
}
Console.WriteLine();
}
}
程序首先声明并实现了一个AuthorAttribute特征类,特征类必须直接或间接继承自抽象类System.Attribute,这往往是我们在使用特征时的第一步。它将是我们提供描述性信息的数据结构。值得指出的是AuthorAttribut特征类名中的后缀“Attrubute”是C#推荐的特征类的命名方式,并非必须如此。
接下来在我们实现自己的类MyClass的时候,我们便采用前面的AuthorAttribute特征类来为MyClass提供描述信息。特征实例化语句[Author("Anders Hejlsberg")]帮助我们完成这一行为。这里的中括号“[ ]”告诉C#我们在进行特征实例化。Author是AuthorAttribute类的去掉后缀“Attrubute”的简写形式——C#编译器可以自动识别,当然我们将Author换成AuthorAttribute后效果是一样的。其中("Anders Hejlsberg")将调用AuthorAttribute特征类的AuthorAttribute(string name) 实例构造器。通过这样的特征实例化语句后,我们就给我们的MyClass类提供了一个“撰写者”(AuthorAttribute)的特征描述。在Test测试类中,我们便可以通过映射(下面的专题将予以详述)来获得我们先前设定的描述信息。运行程序可以得到下面的结果:The author of MyClass is : Anders Hejlsberg 。
参数
特征实例化时有两种参数,一种如我们上面的由特征类的实例构造器定义的参数,称为“位置参数”。另一种是由特征类中给定义的实例公有域或实例属性的赋值的参数,称为“指定参数”,看下面的特征类的实现:
public class HelpAttribute: Attribute {
public HelpAttribute(string url) { this.url = url; }
public string Topic = null;
private string url;
private int number=-1;
public string Url { get { return url; }}
public int Number {
get { return number; }
set { number=value; }
}
}
对于这样一个特征类的实例化,我们往往采用下面的语句:[Help("http://www.ccw.com.cn",Topic="A demo class",Number=120)]。其中http://www.ccw.com.cn是位置参数。而Topic="A demo class"是指定参数,对应HelpAttribute类中的Topic公有域。Number=120也是指定参数,对应HelpAttribute类中的Number公有属性。
位置参数和相应的特征类的实例构造器紧密相关——构造器提供了什么样的参数构造方式,位置参数就对应什么样的形式。位置参数不可省略,当然如果特征类提供了无参数的构造器,那应该另当别论。指定参数对应着特征类的实例公有域或实例属性,它在实例化的时候并非必须,可以省略。
位置参数和指定参数对它们的类型也有要求,这主要是要能保证在中括号“[ ]”实例化语句中能完成各个参数的初始化。它们必须是下列类型之一:八种整数类型sbyte, short, int, long,byte, ushort, uint和ulong;字符类型char和布尔类型bool;两种浮点类型float 和double;System.Object(即object)和System.Type(typeof操作符返回值);枚举类型。
AttributeUsage特征类
AttributeUsage特征类是C#保留的三个特征类之一,其他两个是条件特征类ConditionalAttribute和废弃特征类ObsoleteAttribute。
AttributeUsage特征类用来描述我们的自己实现的特征类“怎样描述其他类”,比如我们实现的特征类用于描述方法,还是类,接口?另外是否允许我们的特征类对同一个编程元素进行多次描述(比如一个类的撰写者可能有多个)?一个类在被我们的特征类描述后,这种描述是否能够体现在他的继承子类上?AttributeUsage特征类通过三个属性来体现上述的行为:ValidOn,AllowMultiple,Inherited。下面是特征类的原型:
[AttributeUsage(AttributeTargets.Class)]
public class AttributeUsageAttribute: Attribute
{
public AttributeUsageAttribute(AttributeTargets validOn) {...}
public virtual bool AllowMultiple { get {...} set {...} }
public virtual bool Inherited { get {...} set {...} }
public virtual AttributeTargets ValidOn { get {...} }
}
其中AttributeTargets是一个枚举类型,可以指定各种描述目标。需要指出的是AttributeUsage特征类只能够用于直接或间接继承自System.Attribute的类,否则编译器会报错。
下面的例子演示了AttributeUsage特征类的典型用法:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorAttribute: Attribute {
public AuthorAttribute(string name) {
this.name = name;
}
public string Name { get { return name; }}
private string name;
}
其中指定了AuthorAttribute特征类只能用于描述类(AttributeTargets.Class),并且它允许用多个AuthorAttribute特征类来描述同一个编程元素(AllowMultiple = true)。这里并没有指定Inherited的值,它默认为真(true),也就是允许继承子类获得父类的描述。实际上对于一个没有用AuthorAttribute描述的特征类:
class X Attribute: Attribute{ ... }
它相当于
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
class X Attribute: Attribute{ ... }
被AttributeUsage描述的特征类,必须严格按照指定的描述信息去描述其他编程元素,否则会引起编译器报错!
条件特征类ConditionalAttribute定义在某些条件下才能够产生调用的类。废弃特征类ObsoleteAttribute顾名思义是指在某些情况下我们不推荐使用旧有的被废弃的类型或方法等编程元素,这时我们在他的前面加上废弃特征类从而可以通知编译器,编译器编译时往往会给出警告信息。