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

前言

上一篇简单的介绍了一下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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值