做者: 钢钢 来源: 博客园 发布时间: 2011-01-09 23:30 阅读: 13439 次 推荐: 12 原文连接 [收藏]html
摘要:纠结地说,这应该算是一篇关于Attribute 的笔记,其中的一些思路和代码借鉴了他人的文笔(见本文底部连接)。可是,因为此文对Attribute 的讲解实在是叫好(自诩一下 ^_^),因此公之于众,但愿能对你们有所帮助。程序员
Attribute与Property 的翻译区别编程
Attribute 通常译做“特性”,Property 仍然译为“属性”。小程序
Attribute 是什么函数
Attribute 是一种可由用户自由定义的修饰符(Modifier),能够用来修饰各类须要被修饰的目标。工具
简单的说,Attribute就是一种“附着物” —— 就像牡蛎吸附在船底或礁石上同样。测试
这些附着物的做用是为它们的附着体追加上一些额外的信息(这些信息就保存在附着物的体内)—— 好比“这个类是我写的”或者“这个函数之前出过问题”等等。this
Attribute 的做用spa
特性Attribute 的做用是添加元数据。
元数据能够被工具支持,好比:编译器用元数据来辅助编译,调试器用元数据来调试程序。.net
Attribute 与注释的区别
举例:
在项目中,有一个类由两个程序员(小张和小李)共同维护。这个类起一个“工具包”(Utilities)的做用(就像.NET Framework中的Math类同样),里面含了几十个静态方法。而这些静态方法,一半是小张写的、一半是小李写的;在项目的测试中,有一些静态方法曾经出过bug,后来又被修正。这样,咱们就能够把这些方面划分红这样几类:
咱们分类的目的主要是在测试的时候能够按不一样的类别进行测试、获取不一样的效果。好比:统计两我的的工做量或者对曾经出过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.");
}
}
}
运行结果以下:
注意:运行结果是由代码中“#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() 方法的代码,截图以下:
能够看出: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");
}
}
}
结果显示以下:
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[上] —— Attribute 初体验
深刻浅出Attribute[中] —— Attribute本质论
12
0