C#基础之Attribute

1.什么是Attribute

  特性简单点理解就是为目标元素添加一些附加信息,这些附加信息我们可以在运行期间以反射的方式拿到。目标元素指的是程序集、模块、类、参数、属性等元素,附加信息指的是特性类中的成员。可以看出特性类其实就是一个数据结构,我们可以将各种各样的信息放入这个类中,并将特性类关联到指定目标元素中,在目标元素中每关联一个特性就创建一个特性类的实例,当然它的作用还不止如此。下面是使用特性的3段代码,分别是3个类。第一个是MyAttribute特性类,第二个是与特性类关联的目标类MyClass,第3个类是主程序类Program。在第一个类中,我使用了AttributeUsage系统特性类,.net还为我们提供了很多固定特性类,比如Serializable、Conditional、DllImport、Flags等。AttributeUsage类的参数作用我代码里已经有了注释,可以添加这个特性也可以不添加,但这个特性类在我这个程序中必须添加。原因是我在第二个类MyClass中添加了4个MyAttribute特性实例,而默认情况下只允许添加一个实例。因此我得在AttributeUsage中指定AllowMultiple为true,如果不指定编译会报错。

  在AttributeUsage特性类的参数中,并不是完全以传值的形式创建的实例,其中还可以直接给参数赋值比如AllowMultiple和Inherited。对于前者一般是构造函数中的参数,我们必须给构造函数赋值否则就不能初始化了,因此这种类型的参数是固定参数。对于后者则是可选参数,细心点会发现它就是特性类中的属性,比如在第二个类MyClass中就给Hobby属性赋值了。正因为参数里有可选参数,故MyClass可以同时关联4个特性实例。在第三个类中,我通过GetCustomAttribute拿到定制的特性数组,其实就是MyAttribute的实例,这样就可以通过实例对象获取里面的数据成员了。这便是Attribute的基本使用,由于使用时需要调用构造函数,因此定制特性类必须有公共构造函数。将程序的exe文件放入Reflector中可以很清楚的看到特性就是一个类,使用特性其实就是调用特性的构造函数。

 //特性也可以用在特性上
    [AttributeUsage(
        AttributeTargets.All,     //目标元素可以是任何元素
        AllowMultiple=true,     //多个特性可以加在一个元素上,为false时一个元素上只允许有这个特性的唯一实例
        Inherited=true)]           //特性可被子类继承,为false时不可以被继承
    class MyAttribute : Attribute
    {
        //字段
        public string name="默认名字";
        public int age = 0;
        string hobby="默认爱好";

        //属性
        public string Hobby
        {
            get { return hobby; }
            set { hobby = value; }
        }
        //构造方法
        public MyAttribute()
        {

        }
        public MyAttribute(string name, int age)
        {
            this.name = name;
            this.age = age;
        }

        //实例方法
        public void haha()
        {
            Console.WriteLine("哈哈");
        }
    }

    [My()]
    [My(Hobby="足球")]
    [My("小方",20)]
    [My("小白",30,Hobby="篮球")]
    class MyClass
    {
        public void haha()
        {
            Console.WriteLine("我是MyClass类");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            /*
             * 本来想看一下会不会默认拿第一个实例,结果执行时报错:找到同一个类型的多个实例
            //拿到MyClass上的一个特性实例
            MyAttribute myAttribute = (MyAttribute)Attribute.GetCustomAttribute(typeof(MyClass), typeof(MyAttribute));
            //看看这个实例是哪一个
            if (myAttribute!=null)
            {
                Console.WriteLine(myAttribute.name);
                Console.WriteLine(myAttribute.age);
                Console.WriteLine(myAttribute.Hobby);
                myAttribute.haha();
            }
            */

            //拿到MyClass上的特性实例数组,这里有4个MyAttribute的实例
            MyAttribute[] myAttributes = (MyAttribute[])Attribute.GetCustomAttributes(typeof(MyClass), typeof(MyAttribute));
            MyAttribute myAttribute = myAttributes[0];
            if (myAttribute != null)
            {
                Console.WriteLine(myAttribute.name);
                Console.WriteLine(myAttribute.age);
                Console.WriteLine(myAttribute.Hobby);
                myAttribute.haha();
            }
            Console.ReadLine();
        }
    }
    /*执行结果:
                    小白
                    30
                    篮球
                    哈哈
     */

 2.Attribute的作用

  因为特性的存在,让我们可以在程序运行时得到一些信息,再根据这些信息进行逻辑判断。比如可以使用特性来确保Model对象的数据全部去除空字符串,代码如下面所示。第一段代码指特性类MyAttribute,第二段代码指使用特性的MyClass类,第三段代码指MyClass类的扩展方法Trim,第四段代码指主程序类Program。在main方法中创建了一个myclass对象,在给这个对象赋值时特意指定了一些空格。假设现在需要将MyClass这个类作为数据库实体类People,它的实例存放着输入的数据,这个数据可能有空格。一般情况下我得调用trim()方法来去除空格,但是如果MyClass的属性很多的话那就很麻烦了,需要写很多ToString().Trim()方法。而使用特性+扩展方法则可以轻松很多,对于需要进行空格去除的属性添加一个MyAttribute特性,接着调用实例对象的Trim扩展方法。在Trim方法中,会遍历这个对象的所有属性,接着遍历每个属性的所有特性,并找到打了MyAttribute特性的属性,接着进行ToString().Trim()方法的调用并重新给属性赋值,这样只需写一句myclass.Trim()就可以实现除掉空格的功能。如果没有特性,虽然一样可以使用扩展方法来对属性进行去除空格,但是我们无法对指定的属性进行去除,只能一口气把所有类型为string的字符串空格全都去除。

 [AttributeUsage(AttributeTargets.Property,Inherited=false,AllowMultiple=false)]
    public  class TrimAttribute : Attribute
    {
        //字段与属性
        readonly Type myType;
        public Type MyType
        {
            get { return this.myType; }
        }
        //构造函数
        public TrimAttribute(Type type)
        {
            myType = type;
        }
    }

 class MyClass
    {
        [TrimAttribute(typeof(string))]
        public string Name { get; set; }

        [TrimAttribute(typeof(string))]
        public string Hobby { get; set; }

        [TrimAttribute(typeof(string))]
        public string Address { get; set; }
    }

 //扩展方法必须是静态类,静态方法。
    public static class TrimAttributeExtension
    {
        public static void Trim(this object obj)
        {
            Type t = obj.GetType();
            //得到myclass实例对象的所有属性
            foreach (PropertyInfo prop in t.GetProperties())
            {
                //得到某个属性上的所有特性
                foreach(var attr in prop.GetCustomAttributes(typeof(TrimAttribute),true))
                {
                    TrimAttribute trimAttribute = (TrimAttribute)attr;
                    //获得obj的prop属性的值
                    object o=prop.GetValue(obj, null);
                    //如果o不为null且这个属性上的特性实例的MyType属性是string类型
                    if (o!= null && (trimAttribute.MyType == typeof(string)))
                    {
                        //重新给这个属性赋值,也就是已经Trim()后的,可以看到GetPropertyValue(obj, prop.Name)其实就是o。
                        object newValue = GetPropertyValue(obj, prop.Name).ToString().Trim();
                        prop.SetValue(obj, newValue, null);
                    }
                }
            }
        }
        //拿到属性本身所表示的值
        private static object GetPropertyValue(object instance, string propertyName)
        {
            //首先得到instance的Type对象,然后调用InvokeMember方法,
            //这个方法的第一个参数意思是你需要调用的属性、方法、字段的”名字“,第二个参数是你调用propertyName是要干什么,
            //这里是拿到属性,第四个是要操作的实例。最后是需要传入的参数,这里调用属性因此不需要参数我就设置为null了。
            return instance.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, instance, null);
        }
}

 class Program
    {
        static void Main(string[] args)
        {
            MyClass myclass = new MyClass();
            myclass.Name = "小方      ";
            myclass.Hobby = "  篮球     ";
            myclass.Address = "     湖北";
            myclass.Trim();

            /*
            执行到这里会看到上面三个属性的值中空格全部都没有了
            myclass.Name = "小方";
            myclass.Hobby = "篮球";
            myclass.Address = "湖北";
             */
            Console.ReadLine();
        }
    }

3.总体上认识Attribute

  特性,这个描述信息的数据类所描述的信息其实就是元数据。当我们在VS中生成解决方案时,在debug文件夹中就会出现一个exe文件,在windows中它称为可迁移可执行文件PE。PE由3部分组成:PE标头、IL、元数据。PE头主要作用是标识此文件是PE文件并说明在内存中执行程序的入口点。IL不用多说,但有一点要注意IL指令中常有元数据标记。元数据包含元数据表和堆数据结构。一个程序中会有很多类,这些类在PE中都会记录在一个记录类型的元数据表中,此外还有记录方法、字段等成员的元数据表,元数据表也可以引用其他的表和堆。可将这些表理解为数据库中的表,表之间通过主外键来建立一种约束与联系。不过我不知道这些表是如何创建的,是程序中某种成员的所有数据全部放在一起,还是有些数据比如字段是以类为划分的。元数据的堆数据结构有4种,分别是字符串、Blob、用户字符串、GUID。在IL中还有一个元数据标记,可以理解为一个指向元数据的指针,它包含4字节。第一个字节说明这个指针指向的类型,比如是指向类表呢还是指向方法表呢。后3个字节说明指向目标表中的位置,这种感觉有点像zigbee编程。再来看元数据的作用,在程序中定义的所有成员以及外部引入的成员都将在元数据中进行说明,这样在JIT生成机器指令时正是通过元数据中的信息来完成即时编译的。元数据中存储程序中程序集的说明(名称、版本、依赖的其他程序集等),类型的说明(类成员、可访问性、继承实现关系等),特性。到这里可以理解特性是属于PE中的元数据的一部分,具体到物理结构上我觉得是有一个元数据特性表,比如类型元数据表的一个类有一个指针指向它的元数据特性表,这个特性表记录着与这个类关联的所有特性。另外由于特性是作为元数据的一部分,因此特性类将会在编译时就实例化,而不是运行期动态实例化。

时间: 2024-10-10 18:22:59

C#基础之Attribute的相关文章

C#基础系列——Attribute特性使用

前言:上篇 C#基础系列--反射笔记 总结了下反射得基础用法,这章我们来看看C#的另一个基础技术--特性. 1.什么是特性:就博主的理解,特性就是在类的类名称.属性.方法等上面加一个标记,使这些类.属性.方法等具有某些统一的特征,从而达到某些特殊的需要.比如:方法的异常捕捉,你是否还在某些可能出现异常的地方(例如数据库的操作.文件的操作等)经常使用try...catch.这个时候如果使用特性,就可以大大减少方法里面的try...catch的使用.你只需要定义一个专门捕捉异常的特性类Excepti

【基础】Attribute的妙用

一.何为Attribute 下面是微软官方对Attribute的解释: 公共语言运行时允许你添加类似关键字的描述声明,叫做Attributes,它对程序中的元素进行标注,如类型.字段.方法和属性等.Attributes和Microsoft .NET Framework文件的元数据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为. 通俗地理解,就是对目标对象(程序集.类.方法等)进行扩展,使得在运行时可以获取到被扩展对象的额外的信息,通过额外的信息来影响目标对象的行

Attribute

C#基础之Attribute 1.什么是Attribute 特性简单点理解就是为目标元素添加一些附加信息,这些附加信息我们可以在运行期间以反射的方式拿到.目标元素指的是程序集.模块.类.参数.属性等元素,附加信息指的是特性类中的成员.可以看出特性类其实就是一个数据结构,我们可以将各种各样的信息放入这个类中,并将特性类关联到指定目标元素中,在目标元素中每关联一个特性就创建一个特性类的实例,当然它的作用还不止如此.下面是使用特性的3段代码,分别是3个类.第一个是MyAttribute特性类,第二个是

C#基础系列——再也不用担心面试官问我“事件”了

前言:作为.Net攻城狮,你面试过程中是否遇到过这样的问题呢:什么是事件?事件和委托的区别?既然事件作为一种特殊的委托,那么它的优势如何体现?诸如此类...你是否也曾经被问到过?你又是否都答出来了呢?上两篇由浅及深介绍了下委托的用法,这篇还是来说说事件.希望通过这篇的介绍,博友能有个系统的认识,至少应付面试没问题了吧.不信?瞧瞧去~~ C#基础系列目录: C#基础系列——Linq to Xml读写xml C#基础系列——扩展方法的使用 C#基础系列——序列化效率比拼 C#基础系列——反射笔记 C

C#基础系列——一场风花雪月的邂逅:接口和抽象类

前言:最近一个认识的朋友准备转行做编程,看他自己边看视频边学习,挺有干劲的.那天他问我接口和抽象类这两个东西,他说,既然它们如此相像, 我用抽象类就能解决的问题,又整个接口出来干嘛,这不是误导初学者吗.博主呵呵一笑,回想当初的自己,不也有此种疑惑么...今天打算针对他的问题,结合一个实际的使用场景来说明下抽象类和接口的异同,到底哪些情况需要用接口?又有哪些情况需要用抽象类呢? C#基础系列目录: C#基础系列——Linq to Xml读写xml C#基础系列——扩展方法的使用 C#基础系列——序

C#基础系列——小话泛型

前言:前面两章介绍了C#的两个常用技术:C#基础系列——反射笔记 和 C#基础系列——Attribute特性使用 .这一章来总结下C#泛型技术的使用.据博主的使用经历,觉得泛型也是为了重用而生的,并且大部分时候会和反射一起使用.这次还是打算围绕WWH(即What.Why.How)来讲解. 1.什么是泛型:通过参数化类型来实现在同一份代码上操作多种数据类型.利用“参数化类型”将类型抽象化,从而实现灵活的复用.怎么理解呢,其实根据博主的理解,泛型就是将类型抽象化,使用抽象化的类型或对象去实现某些功能

C#基础系列——异步编程初探:async和await

前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了异步并行变成带来了很大的方便.异步编程涉及到的东西还是比较多,本篇还是先介绍下async和await的原理及简单实现. C#基础系列目录: C#基础系列——Linq to Xml读写xml C#基础系列——扩展方法的使用 C#基础系列——序列化效率比拼 C#基础系列——反射笔记 C#基础系列——At

(kobject、ktype、kset,bus_type、device、device_driver)

1.1Linux设备驱动模型简介 1.什么是设备驱动模型 (1)类class.总线bus(负责将设备和驱动挂接起来).设备devices.驱动driver(可以看到在驱动源码中,不管是什么样的驱动,都是以struct_xxx_driver来表示的).Linux设备驱动中的四个框架.分别对应Linux驱动源代码中的四个结构体.四个结构体分别描述Linux设备驱动中的类.总线.设备.驱动,这四个概念.对应的就是设备驱动模型这个概念了,四个模子. (2)kobject和对象生命周期.kobject是L

java web 开发三剑客 -------电子书

Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知Internet的目的是让各个net交互.所以,Internet实质上是将世界上各个国家.各个网络运营商的多个网络相互连接构成的一个全球范围内的统一网,使各个网络之间能够相互到达.各个国家和运营商构建网络采用的底层技术和实现可能各不相同,但只要采用统一的上层协议(TCP/IP)就可以通过Internet