.NET内置特性(二)——自定义特性+反射查看

来源:互联网 发布:php抽象类和接口的作用 编辑:程序博客网 时间:2024/06/11 21:49

前言

上一篇简单的介绍了一下Attribute的基础概念以及应用场景和方法,这一篇文章,我们就来聊聊如何自定义一个特性

实例

需求:

在创建或者更新一个类文件时,需要说明这个类是什么时候由谁创建的,在以后的更新中还要说明什么时候偶是由谁更新的,可以记录也可以不记录更新的内容,以往我们可以通过注释的方式在类上边添加注释:

 //更新:Celine,2017年5月29日,修改了ToString()方法    public class DemoClass {        public override string ToString()        {            return "This is a demo class";        }    }

但是如果有一天想要查看所有类型的更新记录怎么办呢?是不是一个一个的去查看源文件,找出这些注释?

咱们再来回顾一下特性的定义:特性可以用于给类型添加元数据,这些元数据用于描述类型。那么我们是不是可以将这些描述信息通过特性来为类进行添加,在这个例子中,要附加的类型元素应该是:注释类型(“更新”或者“创建”)、修改人、日期、备注信息(可有可无)

1.先创建一个封装了元数据的类RecordAttribute:

public class RecordAttribute:Attribute    {        private string recordType;      //记录类型:更新/创建        private string author;          //作者        private DateTime date;          //更新/创建日期        private string memo;            //备注        //构造函数,构造函数的参数在特性中也成为“位置参数”        public RecordAttribute(string recordType,string author,string date){            //特性类的构造函数的参数有一些限制:必须为敞亮、Type类型,或者是常量数组            //因此不能直接传递DateTime类型,只能传递String类型,然后在构造函数内进行一个强制类型转换            this.recordType=recordType;            this.author = author;            this.date = 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; } }        //构建一个属性,在特性中也叫“命名参数”        public string Memo        {            get;            set;        }    }

这样定义是不够的,因为上看去就是一个普通的类没有任何区别,我们先看一下上一篇中用到的预定义特性: Obsolete是如何写的:

// 摘要:    //     标记不再使用的程序元素。 此类不能被继承。    [Serializable]    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]    [ComVisible(true)]    public sealed class ObsoleteAttribute : Attribute    {        public ObsoleteAttribute();        public ObsoleteAttribute(string message);        ol error);        public bool IsError { get; }        public string Message { get; }    }

注意一下几个特点:

  • 继承自Attribute类
  • 在Obsolete上边有用了三个特性去描述他
    • Serializable:支持序列化
    • AttributeUsage:帮助我们控制定制特性的使用
    • ComVisible :对COM的可访问性

他们是描述元数据的特性,所以可以成为元元数据(meta-metadata)
如果是自定义的特性,我们只需要用到AttributeUsage这一个特性就可以了,那么,我们就来看看AttributeUsage是如何定义的:

  // 摘要:    //     指定另一特性类的用法。 此类不能被继承。    [Serializable]    [AttributeUsage(AttributeTargets.Class, Inherited = true)]    [ComVisible(true)]    public sealed class AttributeUsageAttribute : Attribute    {        public AttributeUsageAttribute(AttributeTargets validOn);        public bool AllowMultiple { get; set; }        public bool Inherited { get; set; }        public AttributeTargets ValidOn { get; }    }

特点:

  • 有一个构造函数,这个构造函数含有一个AttributeTargets的位置参数
  • 有两个命名参数(AllowMultiple、Inherited)

根据特性的书写规范,必须写成一行,位于所应用的目标类型上,就会采用一种特殊的写法:不管是构造函数的参数还是属性,全部写到构造函数的圆括号中,对于构造函数的参数,必须采取构造函数的参数的顺序和类型,因此叫做位置参数;对于属性,采用“属性”=“值”的格式,他们之间用逗号分隔,称作命名参数

我们的RecordAttribute如果写好了,那么在使用的时候就应该是如下所示

[Record("更新", "Celine", "2017年5月29日", Memo="修改ToString(方法)")]public class DemoClass{}

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

在AttributeUsage中的构造函数接受一个AttributeTargets类型的参数,而这个AttributeTargets是一个位标记,他表示这个特性可以加载在哪些类型上,如果写成AttributeTargets.Class,代表可以应用于类这个类型上

//     指定可以对它们应用特性的应用程序元素。    [Serializable]    [ComVisible(true)]    [Flags]    public enum AttributeTargets    {        Assembly = 1,        Module = 2,        Class = 4,        Struct = 8,        Enum = 16,        Constructor = 32,        Method = 64,        Property = 128,        Field = 256,        Interface = 1024,        Delegate = 4096,        ReturnValue = 8192,        GenericParameter = 16384,        All = 32767,    }

AllowMuitple:设置该特性是不是可以重复地添加到一个类型上,例如

    [Record("更新", "Celine", "2017年5月29日", Memo="修改ToString(方法)")]    [Record("更新", "Celine", "2017年5月28日")]    [Record("创建", "Celine", "2017年5月28日")]    public class DemoClass {        public override string ToString()        {            return "This is a demo class";        }    }

Inherited:是否能够被继承

2.实现RecordAttribute

只需要使用AttributeUsage来标注这个类就可以了,主体代码不需要改变

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple=true,Inherited=false)]
 [Record("更新", "Celine", "2017年5月29日", Memo="修改ToString(方法)")]    [Record("更新", "Celine", "2017年5月28日")]    [Record("创建", "Celine", "2017年5月28日")]    public class DemoClass {        public override string ToString()        {            return "This is a demo class";        }    }
static void Main(string[] args)        {            DemoClass demo = new DemoClass();            Console.WriteLine(demo.ToString());            Console.ReadLine();        }    }

3.使用反射来查看元数据

 Type t = typeof(DemoClass);            Console.WriteLine("下面列出应用于{0}的RecordAttribute属性:", t);            //获取所有的ReconrdAttribute特性            object[] records = t.GetCustomAttributes(typeof(RecordAttribute), false);            foreach (RecordAttribute record in records) {                Console.WriteLine("   {0}",record);                Console.WriteLine("     类型:{0}",record.RecordType);                Console.WriteLine("     作者:{0}",record.Author);                Console.WriteLine("     日期:{0}",record.Date.ToShortDateString());                if (!String.IsNullOrEmpty(record.Memo))                {                    Console.WriteLine("     备注:{0}",record.Memo);                }            }            Console.ReadLine();

这里写图片描述

总结

这两篇博客介绍了.NET的两种很重要的应用:特性+反射,特性说的够多了,现在一句话说说反射,反射提供这样几个能力:查看和遍历类型和类型成员的元数据;动态创建类型实例,动态调用所创建的势力的方法,字段,属性(机房中的反射,动态的创建一个接口);迟绑定方法和属性,这里用到的第一个功能,以后有机会在研究别的功能。

阅读全文
0 0