zoukankan      html  css  js  c++  java
  • C#编程(七十一)---------- 自定义特性

    自定义特性

    在说自定义之前,有必要先介绍一些基本的概念.

    元数据:就是C#中封装的一些类,无法修改,类成员的特性被称为元数据中的注释

    1.什么是特性?

    (1)属性和特性的区别

    属性:属性是面向对象思想里所说的封装在类里面的数据字段,Get,Set方法.

    特性:就相当于类的元数据.

    来看看官方解释?

    特性是给指定的某一声明的一则附加的声明性信息。 允许类似关键字的描述声明。它对程序中的元素进行标注,如类型、字段、方法、属性等。从.net角度看,特性是一种 类,这些类继承于System.Attribute类,用于对类、属性、方法、事件等进行描述,主要用在反射中。但从面向对象的级别看,其实Attribute是类型级别的,而不是对象级别。

    Attribute和.net文件的元数据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响程序的行为.

    2.特性的应用

    (1).net中特性用来处理多种问题,比如序列化,程序的安全特性,防止即时编译器对程序代码进行优化从而代码容易调试等等.定值特性的本质上是在一个类的元素上添加附加信息,并在运行其通过反射得带该附加信息(在使用数据实体对象时经常用到)

    (2)Attribute作为编译期的指令时的应用

    Conditional起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译.一般在程序调试的时候使用.

    DllImport:用来标记非.net函数,表明该方法在一个外部的DLL定义.

    Obsolete:这个属性用来标记当前的方法已被废弃,不再使用.

    注意:Attribute是一个类,因此DllImport也是一个类,Attribute类是在编译的时候实例化,而不是想通常那样在运行时实例化.

    CLSCompliant:保证整个程序集代码遵守CLS,否则编译将报错.

    3.自定义特性

    使用AttributeUsage,来控制如何应用新定义的特性.

      [AttributeUsageAttribute(AttributeTargets.All  可以应用到任何元素

          ,AllowMultiple=true, 允许应用多次,我们的定值特性能否被重复放在同一个程序实体前多次。

          ,Inherited=false,不继承到派生

            )]

    特性也是一个类,必须继承于System.Attribute类,命名规范我类名+Attribute.不管直接还是间接继承,都会成为一个特性类,特性类的声明定义了一种可以放置在声明之上新的特性.使用[]语法使用自定义特性,可以使用反射来查看自定义特性.

    案例:

    public class MyselfAttribute:System.Attribute

    不过说实话,特性确实常用到,但是自定义特性几乎用不到,貌似老外喜欢用.

    如果不能自己定义一个特性并使用它,我姓你肯定觉得我在忽悠你,假设我们有这样一个很常见的需求:我们在创建或者更新一个类文件时,需要说要这个类时什么时候,由谁创建的,在一行的更新中还要说明在什么时候由谁更新的,可以记录也可以不记录更新的内容,以往的情况你会怎么做?肯定想到了添加注释:

    //更新:张三,2015-2-3,修改了ToString()方法

    //更新:李四,2014-4-5

    //创建:王五,2011-7-8

    public class DemoClass

    {

    //dosomething

    }

    这样做没问题,想要啥,就写啥,看起来很好啊,借用金星老师的一句话就是完美!

    but,如果我们有一天想把这些记录保存到数据库中作为备份呢?你是不是要一条一条的去查看源文件,找出注释,然后在一条条的插入到数据库中呢?

    通过上面特性的定义,我们知道特性可以用来给类型添加元数据(描述书觉得数据,包括数据是否被修改,何时创建,创建人,这些数据可以是一个类,方法,属性),这些元数据可以用于描述类型.那么在此处,特性就会派上用场.那么在本例中,元数据应该是:注释类型(更行或者创建),修改人,日期,备注信息(可有可无).而特性的目标类型是DemoClass类.

    按照对于附加到DemoClass类上的元数据的理解,我们先创建一个封装了元数据的类:

        public class RecordAttribute

        {

            private string recordType;//记录类型:更新或者创建

            private string author;//作者

            private DateTime data;//日期

            private string memo;//备注

            //构造函数的参数在特性中也称为"位置参数"

            public RecordAttribute(string  recordType,string author,string  date)

            {

                this.recordType = recordType;

                this.author = author;

                this.data = Convert.ToDateTime(date);

            }

            //对于位置参数,通常只提供get访问

            public string RecordType { get { return recordType; } }

            public string Author { get { return author; } }

            public DateTime Date { get { return Date; } }

            

            //构建一个属性,在特性中也叫"命名参数"

            public string Memo {

                get { return memo; }

                set { memo = value; }

            }

    }

    注意:构造函数的参数date,必须为一个常量,Type类型,或者是常量数组,所以不能直接传递DateTime类型.

    你会说,这不就是一个类吗?你不能因为后面跟了一个Attribute就摇身一变成了特性.那么这样才能然他成为特性并应用到一个类上面呢?进行下一步之前,咱先来看看.net内置的特性Obsolete是如何定义的:

    namespace System {  

        [Serializable]  

        [AttributeUsage(6140, Inherited = false)]  

        [ComVisible(true)]  

        public sealed class ObsoleteAttribute : Attribute {  

           public ObsoleteAttribute();  

           public ObsoleteAttribute(string message);  

           public ObsoleteAttribute(string message, bool error);  

           public bool IsError { get; }  

           public string Message { get; }  

        }  

    }   

    添加特性的格式(位置参数和命名参数)

    首先,我们应该能够发现,他继承自Attribute,这说明我们的RecordAttribute也应该继承自Attribute类.(一个特性类和普通类的区别是:继承了Attribute类)

    其次,我们发现在这个特性的定义上,又用了三个特性去描述他.这三个特性分别是:Serializable,AttributeUsage和ComVisible .  Serializable和ComVisible特性比较简单,就是一个标志,AttributeUsage比较重要,有三个重要的参数可以设置,上面说了.

    这里我们应该可以注意到:特性本身就是用来描述数据的元数据,而这三个特性又用来描述特性,所以他们是”元数据的元数据”(元元数据).

    从这里我们可以看出,特性类本身也可以用除自身以外的其他特性来描述,所以这个特性类的特性数元元数据.

    隐隐我们需要使用元元数据全无描述我们定义的特性RecordAttribute,所以现在我们需要首先了解一下”元元数据”.这里应该记得”元元数据”也是一个特性,大多数情况下,我们只需要掌握AttributeUsage就够了.现在我们深入的研究一下它.先来看一下上面AttributeUsage是如何加载到ObsoleteAttribute特性上面的.

    [AttributeUsage(6140,Inheritedfalse)]

    看看AttributeUsage定义:

    namespace System {  

        public sealed class AttributeUsageAttribute : Attribute {  

           public AttributeUsageAttribute(AttributeTargets validOn);  

           public bool AllowMultiple { get; set; }  

           public bool Inherited { get; set; }  

           public AttributeTargets ValidOn { get; }  

        }  

    }

    可以看到,头一个构造函数,这个构造函数含有一个AttributeTargets类型的位置参数(Positional Parameter)validOn,还有两个命名参数(Named Parameter).注意ValidOn属性不是一个命名参数,因为他不包含set访问器,是位置参数.

    这里可能有有疑惑,为啥会这样划分参数,这和特性的使用是相关的,加入AttributeUsageAttribute是一个普通的类,我们一定会这样使用:

    //实例化AttributeUsageAttribute类

    AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class);

    usage.AllowMultiple=true;//设置AllowMultiple属性

    usage.Inherited=false;//设置Inherited属性

    但是,特性只写成一行代码,然后紧靠其所应用的类型(目标类型),那么咋办呢?大牛们想到了:不管是构造函数的参数还是属性,彤彤写到构造函数的圆括号中,对于构造函数的参数,必须按照构造函数参数的顺序和类型;对于属性,采用”属性=值”这样的格式,他们之间用逗号分隔.于是上面的代码就缩减成了下面这样:

    [AttributeUsage(AttributeTargets.Class,AllowMutliple=true,Inherited=false)]

    可以看出,AttributeTargets.Class是构造函数的参数(位置参数),而AllowMutliple和Inherited实际上是属性(命名参数).命名参数是可选的.将来我们的RecordAttribute的使用方式于此相同.(为什么管这些属性叫做作数,可能是因为它们的使用方式看上去更像方法的参数)

    假设现在我们的RecordAttribute已经OK了,则它的使用使用应该是这样的:

    [RecordAttribute(“创建”,”syx”,”2015-8-8”,Memo=”hello,world”)]

    public class DemoClass

    {

    //dosomething

    }

    其中recordType,author和date是位置参数,Memo是命名参数

    C#自定义特性:AttributeTarget位标记

    从AttributeUsage特性的名称上可以看出它用于描述特性的使用方式.具体来说,首先应该是其所标记的特性可以应用于哪些类型或者对象.从上面的代码中可以看到AttributeUsage特性的构造函数接受一个AttributeTargets类型的参数,那么我们现在就来了解一下AttributeTargets.

    AttributeTargets是一个位标记,他定义了特性可以应用的类型和对象.

    [Flags]

    public enum AttributeTargets {

    Assembly = 1,         //可以对程序集应用属性。

    Module = 2,              //可以对模块应用属性。

    Class = 4,            //可以对类应用属性。

    Struct = 8,              //可以对结构应用属性,即值类型。

    Enum = 16,            //可以对枚举应用属性。

    Constructor = 32,     //可以对构造函数应用属性。

    Method = 64,          //可以对方法应用属性。

    Property = 128,           //可以对属性 (Property) 应用属性 (Attribute)。

    Field = 256,          //可以对字段应用属性。

    Event = 512,          //可以对事件应用属性。

    Interface = 1024,            //可以对接口应用属性。

    Parameter = 2048,            //可以对参数应用属性。

    Delegate = 4096,             //可以对委托应用属性。

    ReturnValue = 8192,             //可以对返回值应用属性。

    GenericParameter = 16384,    //可以对泛型参数应用属性。

    All = 32767,  //可以对任何应用程序元素应用属性。

    }

    上述例子中使用的是:

    [AttributeUsage(AttributeTargets.Class,AllowMutiple=true,Inherited=false)]

    而ObsoleteAttribute特性中加载的AttributeUsage是这样的:

    [AttributeUsage(6140,Inherited=false)]

    因为AttributeUsage是一个标记,所以可以使用按位或”|”来进行组合.so,当我们这样写的时候:

    [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)

    意味着既可以将特性应用到类上,也可以应用到接口上.

    注意:这里存在这两个特例,观察上面的AttributeUsage的定义,说明特性还可以加载到程序集Assembly和模块Module上,而这两个属于我们的编译结果,在程序中并不存在这样的类型,我们该如何加载呢?可以使用这样的语法:[assembly:SomeAttribute(parameter list)],另外这条语句必须位于程序语句开始之前。

    C#自定义特性:实现RecordAttribute

    现在实现RecordAttribute应该很轻松了,对于类的主题不需要进行任何的修改,我们只是需要让这个类继承自Attribute类,同时使用AttributeUsage特性标记一下它就可以了(假定我们希望可以对类和方法应用此特性):

    [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple=true,Inherited=false)]

    public class recordAttribute:Attribute

    {

    //主体

    }

    完整代码:

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Threading.Tasks;

    namespace 自定义特性

    {

        class Program

        {

            static void Main(string[] args)

            {

                DemoClass demo = new DemoClass();

                Console.WriteLine(demo.ToString());

                Console.ReadKey();

            }

        }

        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]

        public class RecordAttribute : Attribute

        {

            private string recordType;//记录类型:更新或者创建

            private string author;//作者

            private DateTime data;//日期

            private string memo;//备注

            //构造函数的参数在特性中也称为"位置参数"

            public RecordAttribute(string recordType, string author, string date)

            {

                this.recordType = recordType;

                this.author = author;

                this.data = Convert.ToDateTime(date);

            }

            //对于位置参数,通常只提供get访问

            public string RecordType { get { return recordType; } }

            public string Author { get { return author; } }

            public DateTime Date { get { return Date; } }

            //构建一个属性,在特性中也叫"命名参数"

            public string Memo

            {

                get { return memo; }

                set { memo = value; }

            }

        }

        [Record("更新", "wangwu", "2008-1-20", Memo = "修改 ToString()方法")]

        [Record("更新", "lisi", "2008-1-18")]

        [Record("创建", "zhangsan", "2008-1-15")] 

        public class DemoClass

        {

            public override string ToString()

            {

                return "hello,world!";

            }

        }

    }

    这段程序可能简单的输出”hello,world”.我们的属性也好像使用”//”来注释一样对程序没有任何影响,实际上,我们添加的数据已经作为元数据添加到程序集中.

    至此,一个完整的自定义特性的使用已经完成了,举个例子帮助你理解特性打个比方:你约一个没见过面的网友约会,约好时间地点,怎么解决不认识TA的问题?你们可以约好,手上拿个特别的东西不就解决了。这个特别的、用于标识你所不认识的人的东西,就相当于Attribute了。所以Attribute是用于在运行期动态调用的场合。

    如果仅仅是前面介绍的内容,还是不足以说明Attribute有什么实用价值的话,那么从后面的章节开始我们将介绍几个Attribute的不同用法,相信你一定会对Attribute有一个新的了解。

  • 相关阅读:
    Apache Maven 入门篇 ( 上 )
    dojo 模块之define
    CORBA 入门(转)
    Java纯POJO类反射到Redis,反射到MySQL
    Ant编译SWF、SWC例子脚本
    转:动态加载SWF,并获取其中的类接口
    Centos上虚拟机搭建
    maven+flexmojos编译swf、swc脚本例子
    博客园中使用MathJax yongmou
    emacs自动补全插件autocomplet和yasnippet,安装、配置和扩展 yongmou
  • 原文地址:https://www.cnblogs.com/android-blogs/p/6638984.html
Copyright © 2011-2022 走看看