【C#进阶系列】17 特性Attribute

这个东西有的叫定制特性,然而我喜欢直接叫特性,但是这样的话一些人不知道我说的是什么,如果我说是Attribute的话那么知道的或者用过的就都懂了。

还记得讲到枚举和位标志那一章,关于位标志,有一个[Flags]的用法。

    [ComVisible(true)]
    [Flags]
    public enum FileAttributes
    {
        /***/
    }

这里的ComVisible和Flags就是特性。

特性的作用

利用特性可宣告式地为自己的代码构造添加注解来实现特殊功能。它相当于往元数据表里写附加信息,使得能在运行时查询到这些附加信息,从而动态改变代码的执行方式。

(实际上特性在编译后就是被序列化到元数据表中,然后获取时再反序列化为实例。)

比如下面这些例子:

  • DllImport特性可以应用于方法,告诉CLR该方法的实现位于指定DLL的非托管代码中。
  • Serializable特性可以应用于类型,告诉序列化格式器一个实例的字段可以序列化和反序列化。
  • AssemblyVersion特性可以应用于程序集,设置程序集的版本号。
  • Flags特性应用于枚举类型,枚举类型就成了位标志集合。

特性的应用

应用方式都很简单就是将特性放置在目标元素前的一对方括号中。

应用特性时,C#允许用一个前缀明确指定特性要应用于的目标元素。

 [type:SomeAttr]//应用于类型
    public class People<[typevar:SomeAttr]T> {//应用于泛型
        [field:SomeAttr]//应用于字段
        public Int32 age;

        [return:SomeAttr]//应用于返回值
        [method:SomeAttr]//应用于方法
        public Int32 Eat(
            [param:SomeAttr]//应用于参数
            String foodName) {
            return 1;
        }

        [property:SomeAttr]//应用于属性
        public String SomeProp {
            [method:SomeAttr]//应用于访问器方法
            get { return null; }
        }

        [event: SomeAttr]//应用于事件
        [field: SomeAttr]//应用于编译器生成的字段
        [method:SomeAttr]//应用于编译器生成的add和remove方法
        public event EventHandler DieEvent;

    }

当然还有更上面的assembly和module分别应用于程序集和模块。

特性实际上是一个类的实例。特性类必须直接或者间接从公共抽象类System.Attribute派生。就比如[Flags]特性实际上定义它的特性类为FlagsAttribute,只不过C#允许应用特性的时候省略Attribute以简化代码。

特性的应用还有一种特殊的语法:

[DllImport("Kernel32",CharSet=CharSet.Auto,SetLastError=true)]

命名DllImportAttribute类只有一个接受一个String参数的构造器,但是上述应用中不仅提供了一个String参数,还多给了两个参数。实际上上面的语法中"Kernel32"定位参数,它是强制性的,而另外两个参数叫命名参数,它的作用是允许在构造好的特性对象中设置对象的任何公共字段和属性。

还可以将多个特性放在一个方括号中使用

[SomeAttr,AnotherAttr]

特性的定义

现在让我们去定一个特性

  [AttributeUsage(AttributeTargets.Class,AllowMultiple =false,Inherited =false)]//这个特性用于限定People特性只能用于类上,如果不加这个特性默认是不限制的.后面两个属性为不允许为一个元素多次指定People特性,且元素的People特性不被继承
    public class PeopleAttribute:System.Attribute {//这里类名后面加后缀Attribute是为了符合微软的标准,当然也可以不加
        private string _sex;
        public PeopleAttribute() {

        }
        public string Sex
        {
            get { return _sex; }
        }
    }

特性应该足够简单,因为特性实际上只是一个标识作用,记录一些类的附加信息,而不是写一些很复杂的代码到里面。

特性的字段和属性的类型也很简单,只能用基元类型,Type和枚举类型。(也可以用一维0基数组,但应该尽量避免。)

检测特性

通常特性都是和反射一起玩的,因为一般都是用反射去检测特性的存在,或者去获取特性的信息。(我记得以前自己写ORM的时候就是用反射加特性)

typeof(FileAttributes).IsDefined(typeof(FlagsAttribute), false);
//用于判断FileAttributes这个类是否应用了[Flags]特性,答案当然是true

然而这个仅仅是用于检测特性,实际上我们更多的时候是获取特性里的一些属性的信息,那么就要获取特性实例对象。

object[] arr= typeof(FileAttributes).GetCustomAttributes(typeof(FlagsAttribute), false);

上面的两个例子都是System.Reflection命名空间各个类型类(如:Assembly,MemberInfo,FieldInfo等)定义的方法,里面每个类都提供了IsDefined和GetCustomAttributes方法。

还有一个是System.Reflection.CustomAttributeExtensions这个静态类也提供了一批静态方法去检测,并且更好用。其中GetCustomAttributes直接返回Attribute[]而不是之前的Object[]。

检测特性(不创建从Attribute派生的对象)

前面那些检测方法除了IsDefined外,都会在内部调用特性类的构造器,可能还会调用属性的Get和set访问器方法。如果是首次访问类型还会调用类型构造器。

这些方法或者构造器中,如果有每次查找特性都要执行的代码,那么就会存在安全隐患。(所以说如果特性类足够简单其实不需要用到这种检测特性的)

所以有了System.Reflection.CustomAttributeData类,在查找特性时禁止执行特性类中的代码。

两个特性实例的相互匹配

System.Attribute重写了Object的Equals方法,会在内部比较两个对象的类型。不一致会返回false,一致会利用反射来比较两个特性对象中的字段值(为每个字段调用Equals)。所有字段匹配就返回true否则false。

可在自己定义的特性类中重写Equals来移除反射的使用,从而提高性能。(记得重写Equals时要重写GetHashCode)

System.Attribute还公开了虚方法Match,它的默认实现只是调用Equals并返回结果,然而我们重写它可以实现更多的匹配效果。

条件特性类

System.Diagnostics.ConditionalAttribute特性类称为条件特性类。

#define TroyTest
[Conditional("TroyTest"), Conditional("Verify")]
    public class PeopleAttribute:System.Attribute {
        public PeopleAttribute() {

        }
    }

然后现在如果People特性应用到了某元素如一个类Man上,那么编译后只有当定义了TroyTest或者Verify符号的情况下,才会向Man的元数据中写入特性信息。(不过People类的定义元数据和实现还在程序集中,毕竟它是一个类,只是不向Man的元数据中写附加信息而已)

#define Test这个语法要写在文件最顶部,也就是using上方。

参考#define 用法地点:https://msdn.microsoft.com/zh-cn/library/yt3yck0x.aspx

PS:

这两天换了套博客皮肤,自己也写了部分样式,最6的是拿画图工具改了两张阿狸的图。

看了一下效果,感觉还是蛮有成就感的。

时间: 2024-12-24 20:27:29

【C#进阶系列】17 特性Attribute的相关文章

C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(二)

前言:上篇介绍了下封装BootstrapHelper的一些基础知识,这篇继续来完善下.参考HtmlHelper的方式,这篇博主先来封装下一些常用的表单组件.关于BootstrapHelper封装的意义何在,上篇评论里面已经讨论得太多,这里也不想过多纠结.总之一句话:凡事有得必有失,就看你怎么去取舍.有兴趣的可以看看,没兴趣的权当博主讲了个“笑话”吧. 本文原创地址:http://www.cnblogs.com/landeanfen/p/5746166.html BootstrapHelper系列

【 D3.js 进阶系列 】 进阶总结

进阶系列的文章从去年10月开始写的,晃眼又是4个多月了,想在年前总结一下. 首先恭祝大家新年快乐.今年是羊年吧.前段时间和朋友聊天,聊到十二生肖里为什么没猫,我张口就道:不是因为十二生肖开会的时候猫迟到了吗? 呵呵,不知道这是谁给我灌输的观点.o(>﹏<)o 进阶系列的文章分为两部分,文章前括号里写有: [D3.js 进阶系列] [D3.js 选择集与数据详解] 虽然称之为"进阶",但并不是说一定要看完"入门"才能看.由于本人能力有限,不能很好地整理成由

C#进阶系列——WebApi 接口测试工具:WebApiTestClient

C#进阶系列--WebApi 接口测试工具:WebApiTestClient 前言:这两天在整WebApi的服务,由于调用方是Android客户端,Android开发人员也不懂C#语法,API里面的接口也不能直接给他们看,没办法,只有整个详细一点的文档呗.由于接口个数有点多,每个接口都要详细说明接口作用.参数类型.返回值类型等等,写着写着把博主惹毛了,难道这种文档非要自己写不成?难道网上没有这种文档的展示工具吗?带着这两个问题,在网络世界里寻找,网络世界很奇妙,只要你用心,总能找到或多或少的帮助

C#进阶系列——MEF实现设计上的“松耦合”(四):构造函数注入

前言:今天十一长假的第一天,本因出去走走,奈何博主最大的乐趣是假期坐在电脑前看各处堵车,顺便写写博客,有点收获也是好的.关于MEF的知识,之前已经分享过三篇,为什么有今天这篇?是因为昨天分享领域服务的时候,用到MEF的注入有参构造函数的方法,博主好奇心重,打算稍微深挖一下,这篇来对此知识点做个总结. 还是将前面三篇的目录列出来,对MEF没有了解的朋友,可以先看看: C#进阶系列——MEF实现设计上的“松耦合”(一) C#进阶系列——MEF实现设计上的“松耦合”(二) C#进阶系列——MEF实现设

C# 知识特性 Attribute

C#知识--获取特性 Attribute 特性提供功能强大的方法,用以将元数据或声明信息与代码(程序集.类型.方法.属性等)相关联.特性与程序实体关联后,可在运行时使用"反射"查询特性,获取特性集合方法是GetCustomAttributes(); 根据约定,所有特性名称都以单词"Attribute"结束,以便将它们与".NET Framework"中的其他项区分.但是,在代码中使用特性时,不需要指定 attribute 后缀.在声明特性类时要以

C#进阶系列——WebApi 异常处理解决方案

前言:上篇C#进阶系列——WebApi接口传参不再困惑:传参详解介绍了WebApi参数的传递,这篇来看看WebApi里面异常的处理.关于异常处理,作为程序员的我们肯定不陌生,记得在介绍 AOP 的时候,我们讲过通过AOP可以统一截获异常.那么在我们的WebApi里面一般是怎么处理异常的呢,今天这一篇,博主带着大家一起来实践下WebApi的异常处理. WebApi系列文章 C#进阶系列——WebApi接口测试工具:WebApiTestClient C#进阶系列——WebApi 跨域问题解决方案:C

C#进阶系列——DDD领域驱动设计初探(六):领域服务

前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈这个知识点的使用. DDD领域驱动设计初探系列文章: C#进阶系列——DDD领域驱动设计初探(一):聚合 C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上) C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下) C#进阶系列——DDD领域驱动设计初探

C#进阶系列——WebApi 身份认证解决方案:Basic基础认证

前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想想都恐怖.经过一番折腾,总算是加上了接口的身份认证,在此记录下,也给需要做身份认证的园友们提供参考. WebApi系列文章 C#进阶系列--WebApi接口测试工具:WebApiTestClient C#进阶系列--WebApi 跨域问题解决方案:CORS C#进阶系列--WebApi身份认证解决方

C#进阶系列——WebApi 异常处理解决方案(转)

出处:http://www.cnblogs.com/landeanfen/p/5363846.html 阅读目录 一.使用异常筛选器捕获所有异常 二.HttpResponseException自定义异常信息 三.返回HttpError 四.总结 正文 前言:上篇C#进阶系列——WebApi接口传参不再困惑:传参详解介绍了WebApi参数的传递,这篇来看看WebApi里面异常的处理.关于异常处理,作为程序员的我们肯定不陌生,记得在介绍 AOP 的时候,我们讲过通过AOP可以统一截获异常.那么在我们