特性是一种允许我们向程序集增加元数据的语言结构,它是用于保存程序结构信息的某种特殊类型的类。
根据惯例,特性名使用Pascal命名法并且以Attribute
后缀结尾。当为目标应用特性时,我们可以不使用后缀。例如对于SerializableAttribute
和MyAttributeAttribute
这两个特性,我们在把他们应用到结构时可以使用Serializable
和MyAttribute
短名称。
所有特性类都派生自System.Attribute
,用户自定义的特殊类叫做自定义特性。
声明自定义特性
- 派生自
System.Attribute
- 起一个以后缀为
Attribute
结尾的名字
为安全起见,建议声明一个sealed
的特性类
- 由于特性持有目标的信息,所以特性类的公共成员只能是:字段,属性,构造函数。
使用特性的构造函数
和其他类一样,都有构造函数,每一个特性至少必须有一个公共构造函数,如果不声明构造函数,编译器会为我们产生一个隐式,公共且无参的构造函数,也可以被重载,声明构造函数时,必须使用类全名(即包括后缀)。在应用时,才可以使用短名称(不包括后缀)。
[MyAttribute("Holds a value")] //使用了一个字符串的构造函数,它只是声明语句,只有特性的消费者访问特性时候才能调用构造函数,它不会决定什么时候构造特性类的对象。
public int MyField;
构造函数中的位置参数和命名参数
[MyAttribute("An excellent class",Review="Amy",ver="0.7.1")]
第一个参数是位置参数,后两个是命名参数。
public sealed class MyAttributeAttribute:System.Attribute
{
public string Description;
public string Ver;
public string Reviewer;
public MyAttributeAttribute(string desc){
Description=desc;
}
}
[MyAttribute("Excellent class",Reviewer="CJ266",Ver="0.7.1")] //虽然构造函数只有一个形参,但我们可以通过命名参数给构造函数3个实参,这与普通的类是不一样的。
class MyClass{
}
上述代码表示,构造函数的声明只列出一个形参,但我们可以通过命名参数给构造函数3个,但需要注意的是,构造函数需要的任何位置参数都必须放在命名参数之前。
限制特性
特性本身就是类,有一个很重要的预定义特性可以应用到自定义特性上,那就是AttributeUsage
特性,可以用它来限制特性使用在某个目标类型上。
例如,如果我们希望自定义特性MyAttribute
只应用到方法上,那么可以以如下方式使用AttributeUsage
:
[AttributeUsage(AttributeTarget.Method)]
public sealed class MyAttributeAttribute:System.Attribute{...}
AttributeUsage
有三个重要的公共属性:
名字 | 意义 | 默认值 |
---|---|---|
ValidOn |
限制特性能应用的目标类型的列表,构造函数的第一个参数必须是AttributeTarget 类型的枚举值 |
|
Inherited |
一个布尔值,指示特性是否会被装饰类型的派生类所继承 | true |
AllowMultiple |
一个指示目标是否被应用多个特性的实例的布尔值 | false |
AttributeTarget
的枚举值成员:
All |
Assembly |
Class |
Constructor |
---|---|---|---|
Delegate |
Enum |
Event |
Field |
GenericParameter |
Interface |
Method |
Module |
Parameter |
Property |
ReturnValue |
Struct |
在使用AttributeUsage
时,构造函数至少需要一个参数,参数包含的目标类型会保存在ValidOn
中,还可以通过命名参数有选择地设置Inherited
和AllowMultiple
属性。
访问特性
我们可以通过Type
对象获取了解有关类型的几乎所有信息:
成员 | 成员类型 | 描述 |
---|---|---|
Name |
属性 | 返回类型的名字 |
Namespace |
属性 | 返回包含类型声明的命名空间 |
Assembly |
属性 | 返回声明类型的程序集,如果类型是泛型的,返回定义这个类型的程序集 |
GetFields |
方法 | 返回类型的字段列表 |
GetProperties |
方法 | 返回类型的属性列表 |
GetMethods |
方法 | 返回类型的方法列表 |
对于访问自定义特性来说,我们也可以用Type
的两个方法(IsDefined
和GetCustomAttributes
)
IsDefined
方法
使用IsDefined
方法来判断特性是否应用到了,第一个参数是接受需要检查特性的Type
对象,第二个参数是bool
类型,指示是否搜索继承树来查找这个特性。
GetCustomAttributes
方法
该方法返回的对象是object
的数组,因此我们必须强制转换为相应的特性类型,布尔参数指定是否搜索继承树来查找特性。
[AttributeUsage(AttributeTargets.Class)]
public sealed class ReviewCommentAttribute : System.Attribute
{
public string Description { get; set; }
public string VersionNumber { get; set; }
public string ReviewerID { get; set; }
public ReviewCommentAttribute(string desc,string ver)
{
Description = desc;
VersionNumber = ver;
}
}
class BaseClass
{
public int BaseField = 0;
}
[ReviewComment("This is Derived","0.8.1")]
class DerivedClass : BaseClass
{
public int DerivedField = 0;
}
class Program1
{
static void Main()
{
var bc = new BaseClass();
var dc = new DerivedClass();
BaseClass[] bca = new BaseClass[] { dc, bc };
foreach (var v in bca)
{
Console.WriteLine("object type:{0}", v.GetType().Name);
var fi = v.GetType().GetFields();
Console.WriteLine($"IsDefined:{v.GetType().Name}:{v.GetType().IsDefined(typeof(ReviewCommentAttribute), false)}");
foreach (var f in fi)
{
Console.WriteLine("Field:{0}", f.Name);
}
}
var t = dc.GetType();
var Attrs = t.GetCustomAttributes(true);
foreach (var attr in Attrs)
{
var attr1 = attr as ReviewCommentAttribute;
Console.WriteLine($"{attr1.Description}");
}
}
}