Attribute是什么?

  Attribute(特性)是用来为程序实体(class、method、property等)提供描述性信息的类。作为程序的一部分,Attribute会被编译进Assembly(程序集)Metadata(元数据)里面。此外,通过使用Reflection,我们还可以在程序运行时从Metadata里获取某个特定的程序实体上附加的Attribute以决策程序的运行。

使用Attribute

  1. 在C#中,通过使用方括号[]将Attribute名称括起来置于使用该Attribute的程序实体的声明前方以指定Attribute。

    1
    2
    3
    4
    [Serializable]
    public class SampleClass{

    }
  2. 如下方示例所示,我们也可以同时将多个Attribute附加到一个程序实体的声明上

    1
    2
    3
    void MethodA([In][Out] ref double x){

    }
  3. 对于给定实体,一些Attribute可以指定多次

    1
    2
    3
    4
    [Conditional("DEBUG"), Conditional("TEST1")]
    void TestMethod(){

    }
  4. 许多Attribute都有参数,可以在Attribute名称后加上()为Attribute指定参数。Attribute的参数可以是Positional paramter(位置参数)Unnamed paramter(未命名参数)或者Named paramter(已命名参数)。其中Positional paramter对于Attribute的构造函数参数,必须以构造函数声明时的顺序进行指定,并且不能省略。Named Paramter是可选参数,对应该特性的Property(属性)或者Field(字段)

    1
    [DllImport("user32.dll", SetLasetError = false)]
  5. 默认情况下,Attribute应用于它后面紧挨着的元素,不过我们也可以显示指定Attribute的目标。例如我们可以标识一个Attribute是应用于方法,还是该方法的参数或返回值。
    显示标识Attribute的目标的语法为:[target: attribute-name],下表列出了可能的target

目标值 适用对象
assembly 整个程序集
module 当前程序集模块
field 类或者机构中的字段
event 时间
method 方法或者SetGet属性访问器
param 方法参数或Set访问器参数
property 属性
return 方法、属性索引器或者Get属性访问器的返回值
type 结构、类、枚举或者委托
1
2
3
4
[return: ValidatedContract]
int TestMethod(){
return 0;
}

自定义Attribute

  C#允许创建自定义Attribute,自定义Attribute类是直接或者间接派生自System.Attribute的类。创建自定义Attribute类的规则如下:

  • 类名是该自定义Attribute的名称
  • 构造函数的参数是该自定义Attribute的位置参数
  • 所有公共读写字段公共读写属性都是该自定义Attribute的命名参数(如果Attribute类包含属性,那么该属性必须为读写属性)
  • 按照约定,所有Attribute名称均以’Attribute’一词结尾,不过在代码中使用Attribute时,不需要指定Attribute这一后缀,例如[AttributeUsage]等同于AttributeUsageAttribute
  • 可以在自定义Attribute类前加上System.AttributeUsage这一Attribute来指定自定义Attribute会对哪些程序实体起作用,以及确定该自定义Attribute能否在同一程序实体上重复应用等,需要注意的是,第一个System.AttributeUsage参数必须是System.AttributeTargets枚举的一个或者多个元素。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = true)]  
    public class AuthorAttribute : System.Attribute
    {
    private string name;
    public double version;

    public AuthorAttribute(string name)
    {
    this.name = name;
    version = 1.0;
    }
    }

根据上面的定义,AuthorAttribute拥有一个位置参数name和一个命名参数version,可以用于classstruct这两中程序实体,并且可以在同个程序实体上多次使用

1
2
3
4
5
6
7
//Author或者AuthorAttribute都可以
[Author("P. Ackerman", version = 1.1)]
[Author("R. Koch", version = 1.2)]
class SampleClass {
// P. Ackerman's code goes here...
// R. Koch's code goes here...
}

使用Reflection获取Attribute

  前面提到我们可以使用Reflection在程序运行时从Metadata里获取某个特定的程序实体上附加的Attribute,其中我们主要使用的方法为GetCustomAttributes,它返回Attribute对象数组。
  在了解如何获取Attribute对象之前,我们需要先了解一下Attribute类在什么时候实例化对象。根据上面的描述,我们知道通过使用[],我们可以给一个程序实体附加上Attribute。从概念上来讲,我们可以等价地认为附加在程序实体上的Attribute就是该Attribute类的一个对象,也就是说

code1
1
2
[Author("P. Ackerman", version = 1.1)] 
class SampleClass

在概念上等价于

code2
1
2
AuthorAttribute annonymousObj = new AuthorAttribute("P. Ackerman");
annonymousObj.version = 1.1;

  但是,需要注意的是,在对Attribute附加的程序实体使用GetCustomAttributes之前,code2并不会被执行。只有在对Attribute附加的程序实体使用GetCustomAttributes的时候,才会调用Attribute类的构造函数实例化Attribute对象。
  也就是说,根据code1,对SampleClass调用GetCustomAttributes会让AuthorAttribute类按照code2的方式调用构造函数实例化一个AuthorAttribute。如果SampleClass上附加了其他Attribute,那么将按照code2的方式构造其他Attribute对象,然后GetCustomAttributes以对象数组的形式返回AuthorAttribute和其他Attribute类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = true)] 
public class AuthorAttribute : System.Attribute {
string name;
public double version;

public AuthorAttribute(string name) {
this.name = name;

// 设置默认值
version = 1.0;
}

public string GetName() {
return name;
}
}

// 使用一个AuthorAttribute
// version为命名参数,可选
[Author("P. Ackerman")]
public class FirstClass {
// ...
}

// 不使用AuthorAttribute
public class SecondClass {
// ...
}

// 使用多个AuthorAttribute
[Author("P. Ackerman"), Author("R. Koch", version = 2.0)]
public class ThirdClass {
// ...
}

class TestAuthorAttribute {
static void Test() {
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}

private static void PrintAuthorInfo(System.Type t) {
System.Console.WriteLine("Author information for {0}", t);

//使用 reflection.
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t);

//输出结果
foreach (System.Attribute attr in attrs) {
if (attr is Author) {
Author a = (Author)attr;
System.Console.WriteLine(" {0}, version {1:f}", a.GetName(), a.version);
}
}
}
}
/* Output:
Author information for FirstClass
P. Ackerman, version 1.00
Author information for SecondClass
Author information for ThirdClass
R. Koch, version 2.00
P. Ackerman, version 1.00
*/

本文参考链接

C# 特性