C# 特性详解
特性(Attribute)是⽤于在运⾏时传递程序中各种元素(⽐如类、⽅法、结构、枚举、组件等)的⾏为信息的声明性标签。您可以通过使⽤特性向程序添加声明性信息。⼀个声明性标签是通过放置在它所应⽤的元素前⾯的⽅括号([ ])来描述的。
特性(Attribute)⽤于添加元数据,如编译器指令和注释、描述、⽅法、类等其他信息。
特性分别预定义特性和⾃定义特性两种。
1.预定义特性:
在C#中,有⼀个⼩的预定义特性集合。在学习如何建⽴我们⾃⼰的定制特性(custom attributes)之前,我们先来看看在我们的代码中如何使⽤预定义特性。
1using System;
2public class AnyClass
3 {
4 [Obsolete("Don't use Old method, use New method", true)]
5static void Old( ) { }
6static void New( ) { }
7public static void Main( )
8 {
9 Old( );
@奕剑晗日和资料大全
10 }
11 }
我们先来看⼀下上⾯这个例⼦,在这个例⼦中我们使⽤了Obsolete特性,它标记了⼀个不应该再被使⽤的程序实体。第⼀个参数是⼀个字符串,它解释了为什么该实体是过时的以及应该⽤什么实体来代替它。实际上,你可以在这⾥写任何⽂本。第⼆个参数告诉编译器应该把使⽤这个过时的程序实体当作⼀种错误。它的默认值是false,也就是说编译器对此会产⽣⼀个警告。如果是true,则是产⽣⼀个错误。
当我们尝试编译上⾯这段程序的时候,我们将会得到⼀个错误:
AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'
2.开发定制特性(custom attributes)
现在让我们来看看如何开发我们⾃⼰的特性。
⾸先我们要从System.Attribute派⽣出我们⾃⼰的特性类(⼀个从System.Attribute抽象类继承⽽来的类,不管是直接还是间接继承,都会成为⼀个特性类。特性类的声明定义了⼀种可以被放置在声明之上新的特性)。
1using System;
2public class HelpAttribute : Attribute
3 {
4 }
不管你是否相信,我们已经建⽴了⼀个定制特性,现在我们可以⽤它来装饰现有的类就好像上⾯我们使⽤Obsolete attribute⼀样。
1 [Help()]
2public class AnyClass
3 {
4 }
@奕剑晗日和资料大全
注意:对⼀个特性类名使⽤Attribute后缀是⼀个惯例,⽐如上⾯的HelpAttribute,Help加上后缀Attribute。然⽽,当我们把特性添加到⼀个程序实体,是否包括 Attribute后缀是我们的⾃由。编译器会⾸先在System.Attribute的派⽣类中查找被添加的特性类。如果没有找到,那么编译器会添加 Attribute后缀继续查找。
到⽬前为⽌,这个特性还没有起到什么作⽤。下⾯我们来添加些东西给它使它更有⽤些。
1using System;
2public class HelpAttribute : Attribute
3 {
4public HelpAttribute(String Descrition_in)
5 {
6this.description = Description_in;
7 }
8protected String description;
9public String Description
10 {
11get
12 {
13return this.description;
14 }
15 }
16 }
17 [Help("this is a do-nothing class")]
18public class AnyClass
19 {
20 }
在上⾯的例⼦中,我们给HelpAttribute特性类添加了⼀个属性并且在后续的部分中我们会在运⾏时环境中查寻它。
定义或控制特性的使⽤
AttributeUsage类是另外⼀个预定义特性类,它帮助我们控制我们⾃⼰的定制特性的使⽤。它描述了⼀个定制特性如和被使⽤。
AttributeUsage有三个属性,我们可以把它放置在定制属性前⾯。第⼀个属性是:
ValidOn
通过这个属性,我们能够定义定制特性应该在何种程序实体前放置。⼀个属性可以被放置的所有程序实体在AttributeTargets enumerator中列出。通过OR操作我们可以把若⼲个AttributeTargets值组合起来。
AllowMultiple
这个属性标记了我们的定制特性能否被重复放置在同⼀个程序实体前多次。
Inherited
我们可以使⽤这个属性来控制定制特性的继承规则。它标记了我们的特性能否被继承。
下⾯让我们来做⼀些实际的东西。我们将会在刚才的Help特性前放置AttributeUsage特性以期待在它的帮助下控制Help特性的使⽤。
@奕剑晗日和资料大全
1using System;
2 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
3public class HelpAttribute : Attribute
4 {
5public HelpAttribute(String Description_in)
6 {
7this.description = Description_in;
8 }
9protected String description;
10public String Description
11 {
12get
13 {
14return this.description;
15 }
16 }
17 }
先让我们来看⼀下AttributeTargets.Class。它规定了Help特性只能被放在class的前⾯。这也就意味着下⾯的代码将会产⽣错误:
1 [Help("this is a do-nothing class")]
2public class AnyClass
3 {
4 [Help("this is a do-nothing method")] //error
5public void AnyMethod()
6 {
7 }
8 }
编译器报告错误如下:
AnyClass.cs: Attribute 'Help' is not valid on this declaration type.
It is valid on 'class' declarations only.
我们可以使⽤AttributeTargets.All来允许Help特性被放置在任何程序实体前。可能的值是:
Assembly,Module,Class,Struct,Enum,Constructor,Method,Property,Field,Event,Interface,Parameter,Delegate
All = Assembly | Module | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate ClassMembers = Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface
下⾯考虑⼀下AllowMultiple = false。它规定了特性不能被重复放置多次。
@奕剑晗日和资料大全
1 [Help("this is a do-nothing class")]
2 [Help("it contains a do-nothing method")]
3public class AnyClass
4 {
5 [Help("this is a do-nothing method")] //error
6public void AnyMethod()
7 {
8 }
9 }
它产⽣了⼀个编译期错误。
AnyClass.cs: Duplicate 'Help' attribute
Ok,现在我们来讨论⼀下最后的这个属性。Inherited, 表明当特性被放置在⼀个基类上时,它能否被派⽣类所继承。
1 [Help("BaseClass")]
2public class Base
3 {
4 }
5public class Derive : Base
6 {
7 }
这⾥会有四种可能的组合:
1 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
2 [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
3 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
4 [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
第⼀种情况:
如果我们查询(Query)(稍后我们会看到如何在运⾏期查询⼀个类的特性)Derive类,我们将会发现Help特性并不存在,因为inherited属性被设置为false。第⼆种情况:
和第⼀种情况相同,因为inherited也被设置为false。
第三种情况:
为了解释第三种和第四种情况,我们先来给派⽣类添加点代码:
@奕剑晗日和资料大全
1 [Help("BaseClass")]
2public class Base
3 {
4 }
5 [Help("DeriveClass")]
6public class Derive : Base
7 {
8 }
现在我们来查询⼀下Help特性,我们只能得到派⽣类的属性,因为inherited被设置为true,但是AllowMultiple却被设置为false。因此基类的Help特性被派⽣类Help特性覆盖了。第四种情况:
在这⾥,我们将会发现派⽣类既有基类的Help特性,也有⾃⼰的Help特性,因为AllowMultiple被设置为true。
定义或控制特性的使⽤AttributeUsage类是另外⼀个预定义特性类,它帮助我们控制我们⾃⼰的定制特性的使⽤。它描述了⼀个定制特性如何被使⽤。
以下是⼀个⼩demo,看这个更好理解:
@奕剑晗日和资料大全
class Program
{
static void Main(string[] args)
{
Demo d = new Demo();
string userName = "Lucy";
MethodInfo mi = d.GetType().GetMethod("Test");
if (mi == null) return;
AllowExecuteAttribute att = Attribute.GetCustomAttribute(mi, typeof(AllowExecuteAttribute)) as AllowExecuteAttribute; if (att == null) return;
if (att.Check(userName))
Console.WriteLine("允许执⾏");
else
Console.WriteLine("不允许执⾏");
Console.ReadKey();
}
}
class Demo
{
[AllowExecute("Jack, Tom")]
public void Test() { }
}
///
/// 标识某⽅法允许执⾏的⽤户
///
[AttributeUsage(AttributeTargets.Method)]
public class AllowExecuteAttribute : Attribute
{
///
///
///
/// 允许执⾏的⽤户名的串联字符串
public AllowExecuteAttribute(string allowedUsers)
{
this._allowedUsers = allowedUsers;
}
private string _allowedUsers;
public bool Check(string userName)
{
return this._allowedUsers.ToLower().IndexOf(userName.ToLower()) > -1;
}
}
2387