`
sangei
  • 浏览: 335716 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

(转)关于C# 中的Attribute 特性

阅读更多
摘要:纠结地说,这应该算是一篇关于Attribute 的笔记,其中的一些思路和代码借鉴了他人的文笔(见本文底部链接)。但是,由于此文对Attribute 的讲解实在是叫好(自夸一下 ^_^),所以公之于众,希望能对大家有所帮助。

  Attribute与Property 的翻译区别

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

  Attribute 是什么

  Attribute 是一种可由用户自由定义的修饰符(Modifier),可以用来修饰各种需要被修饰的目标。

  简单的说,Attribute就是一种“附着物” —— 就像牡蛎吸附在船底或礁石上一样。

  这些附着物的作用是为它们的附着体追加上一些额外的信息(这些信息就保存在附着物的体内)—— 比如“这个类是我写的”或者“这个函数以前出过问题”等等。

  Attribute 的作用

  特性Attribute 的作用是添加元数据。
  元数据可以被工具支持,比如:编译器用元数据来辅助编译,调试器用元数据来调试程序。

  Attribute 与注释的区别

  • 注释是对程序源代码的一种说明,主要目的是给人看的,在程序被编译的时候会被编译器所丢弃,因此,它丝毫不会影响到程序的执行。
  • 而Attribute是程序代码的一部分,不但不会被编译器丢弃,而且还会被编译器编译进程序集(Assembly)的元数据(Metadata)里,在程序运行的时候,你随时可以从元数据里提取出这些附加信息来决策程序的运行。

  举例:

  在项目中,有一个类由两个程序员(小张和小李)共同维护。这个类起一个“工具包”(Utilities)的作用(就像.NET Framework中的Math类一样),里面含了几十个静态方法。而这些静态方法,一半是小张写的、一半是小李写的;在项目的测试中,有一些静态方法曾经出过bug,后来又被修正。这样,我们就可以把这些方面划分成这样几类:

Box

  我们分类的目的主要是在测试的时候可以按不同的类别进行测试、获取不同的效果。比如:统计两个人的工作量或者对曾经出过bug的方法进行回归测试。

  如果不使用Attribute,为了区分这四类静态方法,我们只能通过注释来说明,但这种方式会有很多弊端;

  如果使用Attribute,区分这四类静态方法将会变得简单多了。示例代码如下:

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

  运行结果如下:

2010-12-21_113636

  注意:运行结果是由代码中“#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 的使用方式是完全等价:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->// 长记法
[ConditionalAttribute("LI")]
[ConditionalAttribute(
"NoBug")]
publicstaticvoid Fun()
{ Console.WriteLine(
"Created By Li, NoBug."); }
// 短记法
[Conditional("LI")]
[Conditional(
"NoBug")]
publicstaticvoid Fun()
{ Console.WriteLine(
"Created By Li, NoBug."); }
// 换序
[Conditional("NoBug")]
[Conditional(
"LI")]
publicstaticvoid Fun()
{ Console.WriteLine(
"Created By Li, NoBug."); }
// 单括号叠加
[Conditional("NoBug"), Conditional("LI")]
publicstaticvoid Fun()
{ Console.WriteLine(
"Created By Li, NoBug."); }

  Attribute 的本质

  从上面的代码中,我们可以看到Attribute 似乎总跟public、static 这些关键字(Keyword)出现在一起。

  莫非使用了Attribute 就相当于定义了新的修饰符(Modifier)吗?让我们来一窥究竟!

  示例代码如下:

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

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

2010-12-21_122656

  可以看出: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 类。

  示例代码如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->using System;
namespace Con_Attribute
{
class Program3
{
staticvoid 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")]
publicstring profession;
publicstring Profession
{
get { return profession; }
set { profession = value; }
}
}
//建议取名:HobbyAttribute
class Hobby : Attribute // 必须以System.Attribute 类为基类
{
// 参数值为null的string 危险,所以必需在构造函数中赋值
public Hobby(string _type) // 定位参数
{
this.type = _type;
}
//兴趣类型
privatestring type;
publicstring Type
{
get { return type; }
set { type = value; }
}
//兴趣指数
privateint level;
publicint 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 个可取值。上面这张表是按字母顺序排列的,并不代表它们真实值的排列顺序。

  使用下面这个小程序可以查看每个枚举值对应的整数值,示例代码如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->using System;
namespace Con_Attribute
{
class Program4
{
staticvoid 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");
}
}
}

  结果显示如下:

2010-12-21_232331

  AttributeTargets 使用了枚举值的另一种用法 —— 标识位。
  除了All 的值之外,每个值的二进制形式中只有一位是“1”,其余位全是“0”。
  如果我们的Attribute 要求既能附着在类上,又能附着在类的方法上。就可以使用C# 中的操作符“|”(也就是按位求“或”)。有了它,我们只需要将代码书写如下:

  AttributeTargets.Class  |  AttributeTargets.Method

  因为这两个枚举值的标识位(也就是那个唯一的“1”)是错开的,所以只需要按位求或就解决问题了。

  这样,你就能理解:为什么AttributeTargets.All 的值是32767 了。

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

  大多数情况下,AttributeTargets.All 就已经满足需求了。不过,如果你非要对它有所限制,那就要费点儿周折了。

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->[AttributeUsage(AttributeTargets.Class, AttributeTargets.Field)]
class Hobby : Attribute // 必须以System.Attribute 类为基类
{
// Hobby 类的具体实现
}

  这里是使用Attribute的实例(AttributeUsage)附着在Attribute 类(Hobby)上。Attribute 的本质就是类,而AttributeUsage 又说明Hobby 类可以附着在哪些类型上。

  附加问题:

  1.  如果一个Attribute 类附着在了某个类上,那么这个Attribute 类会不会随着继承关系也附着在派生类上呢?

  2.  可不可以像多个牡蛎附着在同一艘船上那样,让一个Attribute 类的多个实例附着在同一个目标上呢?

  答案:可以。代码如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->[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本质论 

  示例代码

分享到:
评论

相关推荐

    c#中的特性(attribute)+反射的一个例子

    在C#编程语言中,特性(Attribute)是一种元数据,它可以提供有关代码的附加信息,这些信息可以在编译时或运行时被程序访问。特性允许程序员向类、方法、属性等添加自定义标记,以便在后期处理中进行特定操作。另一...

    C#特性Attribute的实际应用之:为应用程序提供多个版本

    在.NET框架中,C#的特性(Attribute)是一种元数据,可以附加到代码的各种元素上,如类、方法、属性等,以提供额外的信息。这些信息可以在运行时被反射机制读取,从而实现各种功能。在标题“C#特性Attribute的实际...

    C# 特性(Attribute).docx

    在C#编程语言中,特性(Attribute)是一种元数据,它可以提供附加信息给编译器、运行时环境或者工具链,以影响代码的行为或者提供额外的描述。这些特性是类的实例,它们属于.NET框架的一部分,允许程序员在不改变...

    c#的attribute实例源码

    C#的Attribute是一种元数据,它允许我们向代码添加额外的信息,这些信息可以在编译...在实际开发中,Attribute广泛应用于各种场景,如数据验证、序列化、代码生成、AOP(面向切面编程)等,是C#中非常强大的一种特性。

    使用C#的Attribute(特性)实现一个简单的ORM

    【内容概要】:在C#中通过学习使用Attribute,实现一个简单的ORM框架。里面主要有两部分的内容,学习使用Attribute获得设定值;学习通过反射获取属性的值 【适应人群】:初级工程师。但需要对反射、Attribute有一定...

    .net中attribute实现方法调用拦截(就是aop)

    标签中的"C#"表明我们讨论的是.NET框架中的C#语言,"attribute"对应上面提到的特性,"aop"则是本文的核心——面向切面编程。在实际项目中,利用Attribute实现AOP可以帮助我们编写更加整洁、可维护的代码,同时减少...

    c#特性(Attribute)简单示例

    C#特性(Attribute)是元数据的一种形式,它允许我们在代码中添加附加信息,这些信息在编译时或运行时可以被编译器、运行时环境或其他工具使用。特性的概念引入,使得开发者能够更灵活地对代码进行注解,为代码提供...

    Visual Studio2017c#中的特性(attribute)+反射源码大全.rar

    在.NET框架中,C#语言提供了强大的特性(Attribute)机制,这是一种元数据,可以附加到程序元素(如类、方法、属性等)上,提供额外的信息。这些信息可以在运行时通过反射来访问和利用,从而实现灵活的代码设计和...

    C# 自定义特性

    在C#编程语言中,特性(Attribute)是一种元数据,它可以提供附加信息到代码的不同元素,如类、方法、属性等。这些元数据在编译时不会直接影响代码的行为,但可以在运行时通过反射机制被程序读取和使用,从而实现...

    C#使用反射获得Attribute(特性)的设定值

    【内容概要】:在C#中使用反射获得Attribute(特性)的设定值。 【涉及的知识点】:自定义Attribute、C#中的反射知识 【使用人群】:初级工程师,适合刚开始学习的人群 【使用场景及目标】:学习如何使用反射获取...

    07.C# 知识回顾 - 特性 Attribute.pdf

    C#中的特性(Attribute)是一种用于提供元数据(Metadata)的声明性标签,它允许开发者在程序中为代码元素(如类、方法、属性等)附加声明性的信息。特性是.NET平台的重要组成部分,主要用于在运行时通过反射...

    c# 自定义特性demo

    在C#编程语言中,特性(Attribute)是一种元数据,它可以提供有关代码的附加信息,这些信息可以在编译时或运行时被程序访问。自定义特性允许开发人员为类、方法、属性等创建自己的元数据标记,从而实现特定的功能或...

    C#特性完整版实例代码

    在C#编程语言中,特性(Attributes)是一种元数据,它们可以向编译器、运行时环境或工具提供额外的信息。这些信息可以用于代码分析、验证、处理或自动生成代码。C#特性允许程序员在代码中添加声明,这些声明可以在...

    c#标签特性简单例子可执行

    在C#编程中,"标签特性"(Attribute)是一种元数据,可以附加到代码的不同元素(如类、方法、属性等)上,提供额外的信息。这些特性在编译时不会影响程序的行为,但在运行时可以通过反射来访问和利用。本案例“c#...

    C#特性Attribute的实际应用之:代码统计分析

    日常工作中,需要为程序集提供统计分析: 1:程序集方法数; 2:开发人员数目及各自所开发或REVIEW的方法数; 3:测试中,被标注有BUG的数目; 4:直接查看方法的IL代码; 鉴于以上统计的需要,特开发本EXE。

    Property和Attribute的区别

    Property是C#中引入的一种语言特性,把类数据成员声明为私有的,而提供公有的方法实现对他们的访问。 Property可以说是一个面向对象的概念,提供了对私有字段的访问封装,在C#中以get和set访问器方法实现对可读可写...

    java代码转c#

    6. **注解与特性**:Java有注解(Annotation),C#有特性(Attribute),两者都是元数据,但C#的特性可以用于运行时反射。 7. **垃圾回收**:虽然两者都有自动垃圾回收机制,但C#提供了更多的控制选项,如`GC....

    C#特性标签实现通用Dao层

    首先,让我们了解C#中的特性(Attribute)。特性是一种元数据,可以附加到程序元素(如类、方法、属性等)上,为编译器或运行时提供额外的信息。它们通常以`[YourCustomAttribute]`的形式出现在代码中。在我们的案例...

    一个实现IOC的小框架 利用Attribute简化Unity框架IOC注入

    本项目通过引入Attribute特性,旨在简化Unity框架的IOC注入过程,使代码更加简洁易读。Attribute在.NET中是一种元数据,可以附加到程序元素(如类、方法、属性等)上,提供额外的信息,通常用于运行时反射和自定义...

    C#基础学习系列之Attribute和反射详解

    Attribute是C#的一种语言特性,用于为各种实体(class,field,property)附加一些说明性信息, 并且可以在运行时环境中检索这些信息(通过反射)。 所有的Attribute必须继承自Attribute类,按照约定,特性类的名称带...

Global site tag (gtag.js) - Google Analytics