[转]关于C# 中的Attribute 特性

正文转自:http://kb.cnblogs.com/page/87531/

作者: 钢钢  来源:
博客园  发布时间: 2011-01-09 23:30  阅读:
9924 次  推荐: 9                 
原稿链接 
[收藏]) 

摘要:纠结地说,这当算是一首关于Attribute
的记,其中的部分思路与代码借鉴了他人的文笔(见本文底部链接)。但是,由于斯文对Attribute
的讲解实在是歌唱(自夸一下 ^_^),所以公的于众,希望能够对大家具有助。

  Attribute与Property 的翻译区别

  Attribute 一般译作“特性”,Property 仍然译为“属性”。

  Attribute 是什么

  Attribute
是相同栽而由于用户擅自定义之修饰符(Modifier),可以为此来修饰各种需要被修饰的靶子。

  简单的说,Attribute就是一致栽“附着物” ——
就比如牡蛎吸附在船底或礁石上同。

  这些沾满物的意向是也它的附着体追加上有分外的信息(这些信息就是封存在巴物的体内)——
比如“这个看似是自己写的”或者“这个函数以前有了问题”等等。

  Attribute 的作用

  特性Attribute 的意向是加加元数据。
  元数据好叫工具支持,比如:编译器用状元数据来助编译,调试器用长数据来调试程序。

  Attribute 与注释的区别

  • 注解是指向程序源代码的一致种植证明,主要目的是让人拘禁的,在程序为编译的时节会受编译器所抛弃,因此,它丝毫非见面影响及程序的实施。
  • 要Attribute是程序代码的等同有些,不但不见面为编译器丢弃,而且还见面吃编译器编译进程序集(Assembly)的冠数据(Metadata)里,在程序运行的下,你时刻可从元数据里提取出这些附加信来决策程序的运转。

  举例:

  于路面临,有一个好像由少数独程序员(小张与小李)共同保障。这个近乎起一个“工具包”(Utilities)的打算(就如.NET
Framework中之Math类一样),里面包含了几十单静态方法。而这些静态方法,一半是稍微张写的、一半凡是有点李写的;在档次之测试中,有部分静态方法曾经产生过bug,后来而被修正。这样,我们就好管这些点划分成这么几近似:

图片 1

  我们分类的目的主要是当测试的上可按照不同的型进行测试、获取不同的效应。比如:统计两单人口的工作量或者对曾经发出过bug的方式开展回归测试。

  如果无下Attribute,为了区别这四类静态方法,我们只好通过注释来验证,但这种艺术会产生为数不少害处;

  如果采取Attribute,区分这四类似静态方法将见面变得简单多矣。示例代码如下:

#define Buged
//C# 的宏定义必须出现在所有代码之前。当前只让 Buged 宏有效。
using System;
using System.Diagnostics; // 注意:这是为了使用包含在此名称空间中的ConditionalAttribute特性
namespace Con_Attribute
{
    class Program
    {
        static void Main(string[] args)
        {
            // 虽然方法都被调用了,但只有符合条件的才会被执行!
            ToolKit.FunA();
            ToolKit.FunB();
            ToolKit.FunC();
            ToolKit.FunD();
        }
    }
    class ToolKit
    {
        [ConditionalAttribute("Li")] // Attribute名称的长记法
        [ConditionalAttribute("Buged")]
        public static void FunA()
        {
            Console.WriteLine("Created By Li, Buged.");
        }
        [Conditional("Li")] // Attribute名称的短记法
        [Conditional("NoBug")]
        public static void FunB()
        {
            Console.WriteLine("Created By Li, NoBug.");
        }
        [ConditionalAttribute("Zhang")]// Attribute名称的长记法
        [ConditionalAttribute("Buged")]
        public static void FunC()
        {
            Console.WriteLine("Created By Zhang, Buged.");
        }
        [Conditional("Zhang")] // Attribute名称的短记法
        [Conditional("NoBug")]
        public static void FunD()
        {
            Console.WriteLine("Created By Zhang, NoBug.");
        }
    }
}

 

  运行结果如下:

图片 2

  注意:运行结果是由于代码中“#define Buged ”这个宏定义所决定。

  分析:

  1.  以本例中,我们采用了ConditionalAttribute
这个Attribute,它叫含有在 System.Diagnostics
名称空间受到。显然,它多半岁月是因此来举行程序调试与确诊的。

  2.  与ConditionalAttribute 相关的凡千篇一律组C#
宏,它们看起与C语言的宏别无第二予,位置要发现在所有C#
代码之前。顾名思义,ConditionalAttribute
是用来判定标准的,凡被ConditionalAttribute
(或Conditional)“附着”了底办法,只有满足了极才见面履。

  3.  Attribute
就比如船底上可以依附很多牡蛎一样,一个方式及也可依附多个ConditionalAttribute
的实例。把Attribute 附着在对象上之写格式很简短,使用方括号将Attribute
括起来,然后随即写Attribute 的附着体就实施了。当多单Attribute
附着在和一个靶及时不时,就将这些Attribute
的方括号一个顺一个地开(或者当一些方括号丁书写多单Attribute),而且不用在乎它们的次第。

  4.  以以Attribute 的早晚,有“长记法”和“短记法”两种植,请君自便。

  由点的第3 条和第4 长达我们得生产,以下四种Attribute
的用办法是完全等价:

// 长记法
[ConditionalAttribute("LI")]
[ConditionalAttribute("NoBug")]
public static void Fun()
{ Console.WriteLine("Created By Li, NoBug."); }
// 短记法
[Conditional("LI")]
[Conditional("NoBug")]
public static void Fun()
{ Console.WriteLine("Created By Li, NoBug."); }
// 换序
[Conditional("NoBug")]
[Conditional("LI")]
public static void Fun()
{ Console.WriteLine("Created By Li, NoBug."); }
// 单括号叠加
[Conditional("NoBug"), Conditional("LI")]
public static void Fun()
{ Console.WriteLine("Created By Li, NoBug."); }

 

  Attribute 的本质

  从点的代码中,我们得以视Attribute 似乎总跟public、static
这些重大字(Keyword)出现在一起。

  莫非使用了Attribute
就相当给概念了初的修饰符(Modifier)吗?让我们来同样窥究竟!

  示例代码如下:

#define XG //C# 的宏定义必须出现在所有代码之前
using System;
using System.Diagnostics; // 注意:这是为了使用包含在此名称空间中的ConditionalAttribute 特性
namespace Con_Attribute
{
    class Program2
    {
        [Conditional("XG")]
        static void Fun()
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("http://xugang.cnblogs.com");
        }
        static void Main(string[] args)
        {
            Fun();
        }
    }
}

 

  使用微软的中间语言反编译器查看 MSIL 中间语言中TargetMethod:void()
方法的代码,截图如下:

图片 3

  可以望:Attribute
本质上便是一个像样,它以所依附的靶子对象上最终实例化。

  仔细察看中语言(MSIL)的代码之后,那些受C#
语言所覆盖的真相,在当中语言(MSIL)中便转换得赤身裸体了。而Attribute
也易得并非秘密!

  图中红色所依的是Fun 方法及其修饰符,但Attribute 并从未起于这边。

  图中蓝色所指的是于调用mscorlib.dll 程序集中System.Diagnostics
名称空间被ConditionalAttribute 类的构造函数。

  可见,Attribute 并无是修饰符,而是一个有异乎寻常实例化形式之近乎!

  Attribute 实例化有什么异常的远在为?

  1.  它的实例是使用.custom 声明的。查看中语言语法,你见面发觉.custom
是特地用来声称起定义特性的。

  2.  声称Attribute 的职务是于函数体内的确实代码(IL_0000  至IL_0014
)之前。

  这虽由“底层”证明了Attribute不是啊“修饰符”,而是同种实例化方式较奇特的接近。

  元数据的意图

  MSIL
中间语言中,程序集的首批数据(Metadata)记录了是序集里有微微只namespace、多少只近乎、类里产生啊成员、成员的拜会级别是呀。而且,元数据是以文件(也就是Unicode
字符)形式存在的,使用.NET的反光(Reflection)技术就能管其读取出来,并摇身一变MSIL
中之树状图、VS 里的Object  Browser
视图,以及电动代码提示作用,这些还是头数据与照技术构成的产物。一个先后集(.EXE或.DLL)能够使含有在协调体内的冠数据来完全地证明自己,而不用像C/C++
那样带来在一样充分捆头文件,这即于作“自包含性”或“自描述性”。

  Attribute 的实例化

  就比如牡蛎天生就要吸附在暗礁或船底上一样,Attribute
的实例一构造出来就是得需要“粘”在一个啊目标上。

  Attribute 实例化的语法是一对一古怪的,主要体现在偏下三触及:

  1.  不使用new
操作符来发生实例,而是用于方括号里调用构造函数来产生实例。

  2.  方括号必需紧挨在放于吃附着目标的眼前。

  3.  因为方括号里空间有限,不能够如用new
那样先构造对象,然后又给目标的特性(Property)赋值。

  因此,对Attribute 实例的性质赋值也当构造函数的圆括号里。

  并且,Attribute 实例化时更要专注的是:

  1. 
构造函数的参数是迟早要描写。有几乎单就是得勾几只,因为你免写的口舌实例就无法组织出。

  2. 
构造函数参数的逐一不能够擦。调用任何函数都非克改参数的各个,除非她发生对应的重载(Overload)。因为是顺序是永恒的,有些写里称该也“定位参数”(意即“个数和岗位固定的参数”)。

  3. 针对Attribute
实例的属性之赋值可有可无。反正它见面起一个默认值,并且属性赋值的逐条不深受限制。有些写里称属性赋值的参数为“具名参数”。

  自定义Attribute 实例

  在是,我们无使用.NET  Framework 中之各种Attribute
系统特性,而是开始自定义一个簇新的Attribute 类。

  示例代码如下:

using System;
namespace Con_Attribute
{
    class Program3
    {
        static void Main(string[] args)
        {
            //使用反射读取Attribute
            System.Reflection.MemberInfo info = typeof(Student); //通过反射得到Student类的信息
            Hobby hobbyAttr = (Hobby)Attribute.GetCustomAttribute(info, typeof(Hobby));
            if (hobbyAttr != null)
            {
                Console.WriteLine("类名:{0}", info.Name);
                Console.WriteLine("兴趣类型:{0}", hobbyAttr.Type);
                Console.WriteLine("兴趣指数:{0}", hobbyAttr.Level);
            }
        }
    }
    //注意:"Sports" 是给构造函数的赋值, Level = 5 是给属性的赋值。
    [Hobby("Sports", Level = 5)]
    class Student
    {
        [Hobby("Football")]
        public string profession;
        public string Profession
        {
            get { return profession; }
            set { profession = value; }
        }
    }
    //建议取名:HobbyAttribute
    class Hobby : Attribute // 必须以System.Attribute 类为基类
    {
        // 参数值为null的string 危险,所以必需在构造函数中赋值
        public Hobby(string _type) // 定位参数
        {
            this.type = _type;
        }
        //兴趣类型
        private string type;
        public string Type
        {
            get { return type; }
            set { type = value; }
        }
        //兴趣指数
        private int level;
        public int Level
        {
            get { return level; }
            set { level = value; }
        }
    }
}

 

  为了不叫代码太丰富,上面的言传身教中Hobby
类的构造函数只来一个参数,所以针对“定位参数”体现的还不够淋漓尽致。大家好为Hobby
类再补充加几独特性,并当构造函数里大多安装几个参数,体验一下Attribute
实例化时对参数个数与参数位置的敏感性。

  可知被Attribute 所依附的目标

  Attribute
可以以协调之实例附着于啊目标达为?这个题目的答案隐藏于AttributeTargets
这个枚举类型里。

  这个类别的但是取值集合为:

All                                        
Assembly                      Class                             
Constructor

Delegate                           Enum                              
Event                              Field

GenericParameter         Interface                        
Method                           Module

Parameter                         Property                        
ReturnValue                Struct

  一共是16
独可取值。上面立张表是按字母顺序排列的,并无意味她真实值的排顺序。

  使用下这个有些序可以翻每个枚举值对应之整数值,示例代码如下:

using System;
namespace Con_Attribute
{
    class Program4
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Assembly\t\t\t{0}", Convert.ToInt32(AttributeTargets.Assembly));
            Console.WriteLine("Module\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.Module));
            Console.WriteLine("Class\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.Class));
            Console.WriteLine("Struct\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.Struct));
            Console.WriteLine("Enum\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.Enum));
            Console.WriteLine("Constructor\t\t\t{0}", Convert.ToInt32(AttributeTargets.Constructor));
            Console.WriteLine("Method\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.Method));
            Console.WriteLine("Property\t\t\t{0}", Convert.ToInt32(AttributeTargets.Property));
            Console.WriteLine("Field\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.Field));
            Console.WriteLine("Event\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.Event));
            Console.WriteLine("Interface\t\t\t{0}", Convert.ToInt32(AttributeTargets.Interface));
            Console.WriteLine("Parameter\t\t\t{0}", Convert.ToInt32(AttributeTargets.Parameter));
            Console.WriteLine("Delegate\t\t\t{0}", Convert.ToInt32(AttributeTargets.Delegate));
            Console.WriteLine("ReturnValue\t\t\t{0}", Convert.ToInt32(AttributeTargets.ReturnValue));
            Console.WriteLine("GenericParameter\t\t{0}", Convert.ToInt32(AttributeTargets.GenericParameter));
            Console.WriteLine("All\t\t\t\t{0}", Convert.ToInt32(AttributeTargets.All));
            Console.WriteLine("\n");
        }
    }
}

 

  结果显示如下:

图片 4

  AttributeTargets 应用了枚举值的别一样栽用法 —— 标识位。   除了All
的价值之外,每个值的二进制形式被不过来一样员是“1”,其余各都是“0”。
  如果我们的Attribute
要求既会蹭在近似及,又能够蹭在类的方齐。就得使用C#
中的操作符“|”(也不怕是遵照位求“或”)。有了其,我们才待将代码书写如下:

  AttributeTargets.Class  |  AttributeTargets.Method

  因为马上半独枚举值的标识位(也尽管是老唯一的“1”)是失去的,所以特待以位求或就化解问题了。

  这样,你虽可知了解:为什么AttributeTargets.All 的价值是32767 了。

  默认情况下,当我们声明并定义一个新的Attribute
类时,它的可附着对象是AttributeTargets.All。

  大多数情景下,AttributeTargets.All
就已经满足急需了。不过,如果你不要本着她装有限制,那就算设费少周折了。

  例如,你想将前面的Hobby
类的巴目标限制为只有“类”和“字段”使用,则示例代码如下:

[AttributeUsage(AttributeTargets.Class, AttributeTargets.Field)]
class Hobby : Attribute // 必须以System.Attribute 类为基类
{
    // Hobby 类的具体实现
}

 

  这里是采取Attribute的实例(AttributeUsage)附着在Attribute
类(Hobby)上。Attribute 的原形就是看似,而AttributeUsage 又证实Hobby
类可以依附于安类型及。

  附加问题:

  1.  设一个Attribute 类附着在了某个类及,那么这Attribute
类会不会见就后续关系吧沾满在派生类及吧?

  2.  可免得以像多只牡蛎附着于同样艘船上那样,让一个Attribute
类的大都个实例附着于跟一个靶及吧?

  答案:可以。代码如下:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = false, AllowMultiple = true)]
class Hobby : System.Attribute
{
    // Hobby 类的具体实现
}

 

  AttributeUsage 这个专门用来修饰Attribute 的Attribute
,除了可控制修饰目标外,还会操纵于其修饰的Attribute
是否可随宿主“遗传”,以及是否可运用多单实例来修饰和一个靶!

  那修饰ConditionalAttribute 的AttributeUsage
又见面是啊体统也?(答案于MSDN中)

  参考来源:

  Attribute 在.NET
编程的施用

  通俗Attribute[上] —— Attribute
初体验

  通俗Attribute[中] ——
Attribute本质论

  演示代码