C# 定制特性

2021-02-12 22:17

阅读:1030

一、初识特性

特性(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‘  

二、特性是什么

    定制特性其实是一个类型的实例,为了符合“公共语言规范”CLS的要求,定制特性类必须直接或间接从公共抽象类System.Atrribute派生。查看文档发现StructLayoutAttribute,MarshalAsAttribute,DllImportAttribute,InAttribute和OutAttribute,这些类都是从System.Attribute派生。所有符合CLS规范的特性类都肯定从这个类派生。

三、定义自己的特性

开发定制特性(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特性类添加了一个属性并且在后续的部分中我们会在运行时环境中查寻它。

四、限制特性

在定义特性时,我们有时会希望他只应用于枚举,或只应用于类,有时我们希望他多少出现,或只出现一次,为了告诉编译器空上特性的合法应用范围,需要向特性类应用System.AttributeUsageAttribute类的实例

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 }
技术分享图片

五、检测定制特性

仅仅定义特性类没有用,确实可以定义自己想要的所有特性类,并应用自己想要的所有实例,但这样除了在程序集中生成额外的元数据,没有其它任何意义。应用程序代码的行为不会有任何改变。现在我们用一种称为反射的技术检测特性的存在,假定你现在让你

根据AnyClass类中是否应用了Help类型的实例来区别重写toString()方法:

 

public override string ToString()
        {
            if (this.GetType().IsDefined(typeof(HelpAttribute), false))
            {
                //如果是,就执行代码
            }
            else
            { 
                //如果不是,就执行代码
            }
        }

 

上述代码调用Type的IsDefined方法,要求系统查看枚举类型的元数据,检查是否关联了HelpAttribute类的实例,如果IsDefined返回true,表明HelpAttribute的一个实例已与枚举类型关联

 


评论


亲,登录后才可以留言!