41. C# -- 特性

 元数据,就是C#中封装的一些类,无法修改.类成员的特性被称为元数据中的注释.

1、什么是特性

     1)属性与特性的区别

         属性(Property):属性是面向对象思想里所说的封装在类里面的数据字段,Get,Set方法。

         特性(Attribute):  官方解释:特性是给指定的某一声明的一则附加的声明性信息。 允许类似关键字的描述声明。它对程序中的元素进行标注,如类型、字段、方法、属性等。从.net角度看,特性是一种 类,这些类继承于System.Attribute类,用于对类、属性、方法、事件等进行描述,主要用在反射中。但从面向对象的级别看,其实Attribute是类型级别的,而不是对象级别。

         Attributes和.net文件的元素据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响程序的行为。

2、特性的应用

    (1).net中特性用来处理多种问题,比如序列化、程序的安全特性、防止即时编译器对程序代码进行优化从而代码容易调试等等。

     定植特性的本质上是一个类的元素上去添加附加信息,并在运行其通过反射得到该附加信息(在使用数据实体对象时经常用到)

    (2)Attribute 作为编译器的指令时的应用

         Conditional:起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译。一般在程序调试的时候使用

         DllImport: 用来标记费.net的函数,表明该方法在一个外部的DLL中定义。

         Obsolete: 这个属性用来标记当前的方法已经废弃,不再使用

       注:Attribute是一个类,因此DllImport也是一个类,Attribute类是在编译的时候实例化,而不是像通常那样在运行时实例化。

         CLSCompliant: 保证整个程序集代码遵守CLS,否则编译将报错。

 

 3、自定义特性

      使用AttributeUsage,来控制如何应用新定义的特性

     [AttributeUsageAttribute(AttributeTargets.All  可以应用到任何元素

      ,AllowMultiple=true, 允许应用多次,我们的定值特性能否被重复放在同一个程序实体前多次。

      ,Inherited=false,不继承到派生

        )]

     特性也是一个类,必须继承于System.Attribute类,命名规范为“类名”+Attribute。不管是直接还是间接继承,都会成为一个特性类,特性类的声明定义了一种可以放置在声明之上新的特性。


代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace CollectionsApplication
{
    //1. 自定义特定:
    //限定特性类的应用范围  
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]  
    //定制MsgAttribute特性类,继承于Attribute  
    public class ClassMsgAttribute : Attribute  
{
        //定义_msg字段和Msg属性//Msg属性用于读写msg字段  
        string _msg;  
        public string Msg { get { return _msg; } set { _msg = value; } }  
        public ClassMsgAttribute() { }  
        //重载构造函数接收一个参数,赋值给_msg字段  
        public ClassMsgAttribute(string s) { _msg = s; }   
}
    //2. 调用:
    //在Person类上标记ClassMsg特性
    [ClassMsg(Msg = "this is the class for  name attribute")]
    class Person
{
        //在_name字段上应用ClassMsg特性
        [ClassMsg("this is name attribute.")]
        string _name;
        //以下特性无法应用,因为MsgAttribute定义的特性只能用于类和字段
        //[ClassMsg("这是读写姓名字段的属性")]
        public string Name { get { return _name; } set { _name = value; } }
}
    public class Test
{
        //3. 主函数情况:
        static void Main(string[] args)
{
            //获取Person类的Type对象tp
            Type tp = typeof(Person);
            //获取tp对象的特性数组,并将数组赋值给MyAtts
            object[] MyAtts = tp.GetCustomAttributes(true);
            //object[] MyAtts = typeof(Person).GetCustomAttributes(false);
            //遍历并输出MyAtts数组子项的Msg属性
            foreach (ClassMsgAttribute m in MyAtts)
{
                Console.WriteLine("Class Person attribute:{0}", m.Msg);
}
            Console.ReadLine();
}
        //运行结果:
        //class person attribute:this is the class for name attribute
}
}

GetCustomAttributes用于获取程序集的特性,也就是这个程序集合中包含了多少个特性

 

继续来一个简单的例子来说明定制特性:

using System;

public class HelpAttribute: Attribute  //定制特性相当于一个类

{

   //...

}

不管你是否相信,我们上面已经建立了一个定制特性,现在我们可以用它来装饰现有的类就好像我们使用的Obsolete attribute一样。

[Help()]

public class AnyClass

{

   //...

}

注意:对于一个特性类使用Attribute后缀是一个惯例。然而,如果不添加编译器会自动添加匹配。

 

定义或控制特性的使用

AttributeUsage类是另外一个预定义特性类,它帮助我们控制我们自己的定制特性的使用。它描述一个定制特性如何被使用。

  

下面通过实例来探讨下AttributeUsage的三个属性

1)我们将会在刚才的Help特性前放置AttributeUsage特性以期待在它的帮助下控制Help特性的使用。

 using System; 
   [AttributeUsage(AttributeTargets.Class), AllowMultiple = false, 
   Inherited = false ] 
   public class HelpAttribute : Attribute 
   { 
   public HelpAttribute(String Description_in) 
   { 
   this.description = Description_in; 
   } 
   protected String description; 
   public String Description 
   { 
   get 
   { 
   return this.description; 
   } 
   } 
   }

先让我们来看一下AttributeTargets.Class。它规定了Help特性只能被放在class的前面。这也就意味着下面的代码将会产生错误:

   [Help("this is a do-nothing class")] 
   public class AnyClass 
   { 
   [Help("this is a do-nothing method")] //error 
   public void AnyMethod() 
   { 
   } 
   }
   //编译器报告错误如下:  
   AnyClass.cs: Attribute 'Help' is not valid on this declaration type. 
   It is valid on 'class' declarations only.   


我们可以使用AttributeTargets.All来允许Help特性被放置在任何程序实体前。可能的值是:

   Assembly,
   Module,
   Class,
   Struct,
   Enum,
   Constructor,
   Method,
   Property,
   Field,
   Event,
   Interface,
   Parameter,
   Delegate,
   All = Assembly   Module   Class   Struct   Enum   Constructor   Method   Property   Field   Event   Interface   Parameter   Delegate,
   ClassMembers = Class   Struct   Enum   Constructor   Method   Property   Field   Event   Delegate   Interface )

下面考虑一下AllowMultiple = false。它规定了特性不能被重复放置多次。

 [Help("this is a do-nothing class")] 
   [Help("it contains a do-nothing method")] 
   public class AnyClass 
   { 
   [Help("this is a do-nothing method")] //error 
   public void AnyMethod() 
   { 
   } 
   }
   它产生了一个编译期错误。 
   AnyClass.cs: Duplicate 'Help' attribute 


Ok,现在我们来讨论一下最后的这个属性。Inherited, 表明当特性被放置在一个基类上时,它能否被派生类所继承。  

   [Help("BaseClass")] 
   public class Base 
   { 
   }
   public class Derive : Base 
   { 
   }
   这里会有四种可能的组合: 
   [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ] 
   [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ] 
   [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ] 
   [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true ]


第一种情况:  

   如果我们查询(Query)(稍后我们会看到如何在运行期查询一个类的特性)Derive类,我们将会发现Help特性并不存在,因为inherited属性被设置为false。   

   第二种情况:  

   和第一种情况相同,因为inherited也被设置为false。  

   第三种情况:  

   为了解释第三种和第四种情况,我们先来给派生类添加点代码:  

   [Help("BaseClass")]
   public class Base
   {
   }
   [Help("DeriveClass")]
   public class Derive : Base
   {
   }

   现在我们来查询一下Help特性,我们只能得到派生类的属性,因为inherited被设置为true,但是AllowMultiple却被设置为false。因此基类的Help特性被派生类Help特性覆盖了。  

   第四种情况: 

   在这里,我们将会发现派生类既有基类的Help特性,也有自己的Help特性,因为AllowMultiple被设置为true。


自定义了一个特性类:

    [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
    class WahAttribute:System.Attribute
    {
        private string description;
        public string Description
        {
            get { return description; }
            set { description = value; }
        }
        private string author;
        public string Author
        {
            get { return author; }
            set { author = value; }
        }
        public WahAttribute(string desc)
        {
            this.description = desc;
        }
    }


运用特性类:

namespace attributeDemo
{
    public class Teacher
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        private int age;
        public int Age
        {
            get { return age; }
            set { age = value; }
        }
        private string sex;
        public string Sex
        {
            get { return sex; }
            set { sex = value; }
        }
        //只有用户名为wah的才可以调用此方法
        [Wah("this is my attribute test", Author = "wah", Description = "test")]
        public string getMessage()
        {
            return "好好学习,天天向上";
        }
        [Wah("this is with parameters test",Author="wanggaihui",Description="test with parameters")]
        public int Test(int a,int b)
        {
            return a+b;
        }
    }
}


处理特性类:

        private void button_Click(object sender, EventArgs e)
        {
            //得到类型
            Type type = typeof(Teacher);
            //得到此类型所有方法
            MethodInfo[] methods = type.GetMethods();
            foreach (MethodInfo method in methods)
            {
                //得到此方法的所有特性
                object[] attributes = method.GetCustomAttributes(false);
                foreach (object o in attributes)
                {
                    //判断是否是自己定义的特性
                    if (o.GetType() == typeof(WahAttribute))
                    {
                        //强转取得值
                        WahAttribute waha = (WahAttribute)o;
                        this.listBox1.Items.Add("author=" + waha.Author);
                        this.listBox1.Items.Add("description=" + waha.Description);
                    }
                }
            }
        }

C#特性可以应用于各种类型和成员。加在类前面的是类特性,加在方法前面的是方法特性。无论他们被用在哪里,无论他们之间有什么区别,特性的最主要的目的就是自描述。并且因为特性是可以由自己定制的,而不仅仅局限于.net提供的那几个现成的,因此给C#程序开发带来了很大的灵活性。

 

我们还是借用生活中的例子来介绍C#的特性机制吧。

假设有一天你去坐飞机,你就必须提前去机场登机处换登机牌。登机牌就是一张纸,上面写着哪趟航班、由哪里飞往哪里以及你的名字、座位号等等信息,其实,这就是特性。它不需要你生理上包含这些属性(人类出现那会儿还没飞机呢),就像上面的HumanBase类没有IsSerializable这样的属性,特性只需要在类或方法需要的时候加上去就行了,就像你不总是在天上飞一样。

 

 


拿到了登机牌,就意味着你可以合法地登机起飞了。但此时你还不知道你要坐的飞机停在哪里,不用担心,地勤人员会开车送你过去,但是他怎么知道你是哪趟航班的呢?显然还是通过你手中的登机牌。所以,特性最大的特点就是自描述。

既然是起到描述的作用,那目的就是在于限定。就好比地勤不会把你随便拉到一架飞机跟前就扔上去了事,因为标签上的说明信息就是起到限定的作用,限定了目的地、乘客和航班,任何差错都被视为异常。如果前面的HumanBase不加上Serializable特性就不能在网络上传输。

  

指定特性参数

如果找到这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到,就生成一个这样的构造函数。如果找到一个这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到就生成一个编译错误。如后面所述,反射会从程序集中读取元数据,并实例化他们表示的特性类。因此,编译器需要确保存在这样的构造函数,才能在运行期间实例化指定的特性。

参考:
https://msdn.microsoft.com/zh-cn/library/67ef8sbd(v=vs.80).aspx
http://zhidao.baidu.com/link?url=FArDop5J7k64OP9APCOZRfRNJ-Y6QcAvI0Y5Dt-4bXh-lxhpImjC1ugmxAv6il9Pc-blnU9PPnsQLpZNr-575q
http://blog.csdn.net/helloguonan/article/details/5912032
https://msdn.microsoft.com/zh-cn/library/z0w1kczw(v=vs.110).aspx
http://www.cnblogs.com/rohelm/archive/2012/04/19/2456088.html

你可能感兴趣的:(C#,编译,特性)