C# 特性详解

C# 特性详解 

    特性(attribute)是被指定给某一声明的一则附加的声明性信息。

    在C#中,有一个小的预定义特性集合。在学习如何建立我们自己的定制特性(custom attributes)之前,我们先来看看在我们的代码中如何使用预定义特性。

 

复制代码
 1 using System; 
2 public class AnyClass
3 {
4 [Obsolete("Don't use Old method, use New method", true)]
5 static void Old( ) { }
6 static void New( ) { }
7 public static void Main( )
8 {
9 Old( );
10 }
11 }
复制代码

 

    我们先来看一下上面这个例子,在这个例子中我们使用了Obsolete特性,它标记了一个不应该再被使用的程序实体。第一个参数是一个字符串,它解释了为什么该实体是过时的以及应该用什么实体来代替它。实际上,你可以在这里写任何文本。第二个参数告诉编译器应该把使用这个过时的程序实体当作一种错误。它的默认值是false,也就是说编译器对此会产生一个警告。

    当我们尝试编译上面这段程序的时候,我们将会得到一个错误:   

    AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'   

 

    开发定制特性(custom attributes)

    现在让我们来看看如何开发我们自己的特性。 
    首先我们要从System.Attribute派生出我们自己的特性类(一个从System.Attribute抽象类继承而来的类,不管是直接还是间接继承,都会成为一个特性类。特性类的声明定义了一种可以被放置在声明之上新的特性)。

 

1 using System; 
2 public class HelpAttribute : Attribute
3 {
4 }

 

  不管你是否相信,我们已经建立了一个定制特性,现在我们可以用它来装饰现有的类就好像上面我们使用Obsolete attribute一样。

 

1 [Help()] 
2 public class AnyClass
3 {
4 }

 

  注意:对一个特性类名使用Attribute后缀是一个惯例。然而,当我们把特性添加到一个程序实体,是否包括 Attribute后缀是我们的自由。编译器会首先在System.Attribute的派生类中查找被添加的特性类。如果没有找到,那么编译器会添加 Attribute后缀继续查找。

    到目前为止,这个特性还没有起到什么作用。下面我们来添加些东西给它使它更有用些。

 

复制代码
 1 using System; 
2 public class HelpAttribute : Attribute
3 {
4 public HelpAttribute(String Descrition_in)
5 {
6 this.description = Description_in;
7 }
8 protected String description;
9 public String Description
10 {
11 get
12 {
13 return this.description;
14 }
15 }
16 }
17 [Help("this is a do-nothing class")]
18 public class AnyClass
19 {
20 }
复制代码

   

    在上面的例子中,我们给HelpAttribute特性类添加了一个属性并且在后续的部分中我们会在运行时环境中查寻它。

 

    定义或控制特性的使用

    AttributeUsage类是另外一个预定义特性类,它帮助我们控制我们自己的定制特性的使用。它描述了一个定制特性如和被使用。 
    AttributeUsage有三个属性,我们可以把它放置在定制属性前面。第一个属性是: 

 

    ValidOn 
    通过这个属性,我们能够定义定制特性应该在何种程序实体前放置。一个属性可以被放置的所有程序实体在AttributeTargets enumerator中列出。通过OR操作我们可以把若干个AttributeTargets值组合起来。


    AllowMultiple 
    这个属性标记了我们的定制特性能否被重复放置在同一个程序实体前多次。

 

    Inherited 
    我们可以使用这个属性来控制定制特性的继承规则。它标记了我们的特性能否被继承。

    下面让我们来做一些实际的东西。我们将会在刚才的Help特性前放置AttributeUsage特性以期待在它的帮助下控制Help特性的使用。 

 

复制代码
 1 using System; 
2 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
3 public class HelpAttribute : Attribute
4 {
5 public HelpAttribute(String Description_in)
6 {
7 this.description = Description_in;
8 }
9 protected String description;
10 public String Description
11 {
12 get
13 {
14 return this.description;
15 }
16 }
17 }
复制代码

 

    先让我们来看一下AttributeTargets.Class。它规定了Help特性只能被放在class的前面。这也就意味着下面的代码将会产生错误: 

 

复制代码
1 [Help("this is a do-nothing class")] 
2 public class AnyClass
3 {
4 [Help("this is a do-nothing method")] //error
5 public 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")]
3 public class AnyClass
4 {
5 [Help("this is a do-nothing method")] //error
6 public void AnyMethod()

7 {
8 }
9 }
复制代码

 

    它产生了一个编译期错误。 
    AnyClass.cs: Duplicate 'Help' attribute

    Ok,现在我们来讨论一下最后的这个属性。Inherited, 表明当特性被放置在一个基类上时,它能否被派生类所继承。

 

复制代码
1 [Help("BaseClass")] 
2 public class Base
3 {
4 }
5 public 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")] 
2 public class Base
3 {
4 }
5 [Help("DeriveClass")]
6 public class Derive : Base
7 {
8 }
复制代码

 

    现在我们来查询一下Help特性,我们只能得到派生类的属性,因为inherited被设置为true,但是AllowMultiple却被设置为false。因此基类的Help特性被派生类Help特性覆盖了。

    第四种情况:

    在这里,我们将会发现派生类既有基类的Help特性,也有自己的Help特性,因为AllowMultiple被设置为true。

    定义或控制特性的使用AttributeUsage类是另外一个预定义特性类,它帮助我们控制我们自己的定制特性的使用。它描述了一个定制特性如何被使用。

 

属性和特性的区别可以参考一下: http://developer.51cto.com/art/200908/147097.htm

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Foxalien/archive/2009/12/05/4946672.aspx

实例探讨:

自定义了一个特性类:

[c-sharp] view plaincopy
  1. [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]  
  2. class WahAttribute:System.Attribute  
  3. {  
  4.     private string description;  
  5.   
  6.     public string Description  
  7.     {  
  8.         get { return description; }  
  9.         set { description = value; }  
  10.     }  
  11.     private string author;  
  12.   
  13.     public string Author  
  14.     {  
  15.         get { return author; }  
  16.         set { author = value; }  
  17.     }  
  18.     public WahAttribute(string desc)  
  19.     {  
  20.         this.description = desc;  
  21.     }  
  22. }  

运用特性类:

[c-sharp] view plaincopy
  1. namespace attributeDemo  
  2. {  
  3.     public class Teacher  
  4.     {  
  5.         private string name;  
  6.   
  7.         public string Name  
  8.         {  
  9.             get { return name; }  
  10.             set { name = value; }  
  11.         }  
  12.         private int age;  
  13.   
  14.         public int Age  
  15.         {  
  16.             get { return age; }  
  17.             set { age = value; }  
  18.         }  
  19.         private string sex;  
  20.   
  21.         public string Sex  
  22.         {  
  23.             get { return sex; }  
  24.             set { sex = value; }  
  25.         }  
  26.         //只有用户名为wah的才可以调用此方法  
  27.         [Wah("this is my attribute test", Author = "wah", Description = "test")]  
  28.         public string getMessage()  
  29.         {  
  30.             return "好好学习,天天向上";  
  31.         }  
  32.         [Wah("this is with parameters test",Author="wanggaihui",Description="test with parameters")]  
  33.         public int Test(int a,int b)  
  34.         {  
  35.             return a+b;  
  36.         }  
  37.     }  
  38. }  

处理特性类:

[c-sharp] view plaincopy
  1. private void button_Click(object sender, EventArgs e)  
  2. {  
  3.     //得到类型  
  4.     Type type = typeof(Teacher);  
  5.     //得到此类型所有方法  
  6.     MethodInfo[] methods = type.GetMethods();  
  7.     foreach (MethodInfo method in methods)  
  8.     {  
  9.         //得到此方法的所有特性  
  10.         object[] attributes = method.GetCustomAttributes(false);  
  11.         foreach (object o in attributes)  
  12.         {  
  13.             //判断是否是自己定义的特性  
  14.             if (o.GetType() == typeof(WahAttribute))  
  15.             {  
  16.                 //强转取得值  
  17.                 WahAttribute waha = (WahAttribute)o;  
  18.                 this.listBox1.Items.Add("author=" + waha.Author);  
  19.                 this.listBox1.Items.Add("description=" + waha.Description);  
  20.             }  
  21.         }  
  22.     }  
  23. }  

C#特性可以应用于各种类型和成员。加在类前面的是类特性,加在方法前面的是方法特性。无论他们被用在哪里,无论他们之间有什么区别,特性的最主要的目的就是自描述。并且因为特性是可以由自己定制的,而不仅仅局限于.net提供的那几个现成的,因此给C#程序开发带来了很大的灵活性。

我们还是借用生活中的例子来介绍C#的特性机制吧。

假设有一天你去坐飞机,你就必须提前去机场登机处换登机牌。登机牌就是一张纸,上面写着哪趟航班、由哪里飞往哪里以及你的名字、座位号等等信息,其实,这就是特性。它不需要你生理上包含这些属性(人类出现那会儿还没飞机呢),就像上面的HumanBase类没有IsSerializable这样的属性,特性只需要在类或方法需要的时候加上去就行了,就像你不总是在天上飞一样。

拿到了登机牌,就意味着你可以合法地登机起飞了。但此时你还不知道你要坐的飞机停在哪里,不用担心,地勤人员会开车送你过去,但是他怎么知道你是哪趟航班的呢?显然还是通过你手中的登机牌。所以,特性最大的特点就是自描述。

既然是起到描述的作用,那目的就是在于限定。就好比地勤不会把你随便拉到一架飞机跟前就扔上去了事,因为标签上的说明信息就是起到限定的作用,限定了目的地、乘客和航班,任何差错都被视为异常。如果前面的HumanBase不加上Serializable特性就不能在网络上传输。

http://developer.51cto.com/art/200908/147097.htm 

http://www.bccn.net/Article/net/cs/jszl/200709/6160_2.html

指定特性参数

如果找到这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到,就生成一个这样的构造函数。如果找到一个这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到就生成一个编译错误。如后面所述,反射会从程序集中读取元数据,并实例化他们表示的特性类。因此,编译器需要确保存在这样的构造函数,才能在运行期间实例化指定的特性。

原文地址:https://www.cnblogs.com/yeagen/p/2947242.html