C++[转]关于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,后来而让修正。这样,我们就是得管这些面划分成这么几类似:

C++ 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.");
        }
    }
}

 

  运行结果如下:

C++ 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()
方法的代码,截图如下:

C++ 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");
        }
    }
}

 

  结果显示如下:

C++ 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本质论

  演示代码