这个东西有的叫定制特性,然而我喜欢直接叫特性,但是这样的话一些人不知道我说的是什么,如果我说是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的是拿画图工具改了两张阿狸的图。

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

posted on 2016-03-30 02:31  韩子卢  阅读(1746)  评论(1编辑  收藏  举报