`
starnc
  • 浏览: 145189 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

c# 特性/属性(Attribute) 以及使用反射查看自定义特性

    博客分类:
  • .NET
阅读更多

 

     可能很多人还不了解特性,所以我们先了解一下什么是特性。想想看如果有一个消息系统,它存在这样一个方法,用来将一则短消息发送给某人:

// title: 标题;author:作者;content:内容;receiverId:接受者Id
public bool SendMsg(string title, string author, string content, int receiverId){
    // Do Send Action
}

我们很快就发现这样将参数一个个罗列到方法的参数列表中扩展性很糟糕,我们最好定义一个Message类将短消息封装起来,然后给方法传递一个Message对象:

public class Message{
    private string title;
    private string author;
    private string content;
    private int receiverId;
    // 略
}
public bool SendMsg(Messag msg){
    // Do some Action
}

 

此时,我们或许应该将旧的方法删除,用这个扩展性更好的SendMsg方法来取代。遗憾的是我们往往不能,因为这组程序可能作为一组API发布,在很多客户程序中已经在使用旧版本的SendMsg()方法,如果我们在更新程序的时候简单地删除掉旧的SendMsg()方法,那么将造成使用老版本SendMsg()方法的客户程序不能工作。

这个时候,我们该如果做呢?我们当然可以通过方法重载来完成,这样就不用删除旧的SendMsg()方法了。但是如果新的SendMsg()不仅优化了参数的传递,并且在算法和效率上也进行了全面的优化,那么我们将会迫切希望告知客户程序现在有一个全新的高性能SendMsg()方法可供使用,但此时客户程序并不知道已经存在一个新的SendMsg方法,我们又该如何做呢?我们可以打电话告诉维护客户程序的程序员,或者发电子邮件给他,但这样显然不够方便,最好有一种办法能让他一编译项目,只要存在对旧版本SendMsg()方法的调用,就会被编译器告知。

1..Net内置特性介绍

        .Net 中可以使用特性来完成这一工作。特性是一个对象,它可以加载到程序集及程序集的对象中,这些对象包括 程序集本身、模块、类、接口、结构、构造函数、方法、方法参数等,加载了特性的对象称作特性的目标。特性是为程序添加元数据(描述数据的数据)的一种机制,通过它可以给编译器提供指示或者提供对数据的说明

 

NOTE:特性的英文名称叫做Attribute,在有的书中,将它翻译为“属性”;另一些书中,将它翻译为“特性”;由于通常我们将含有get和/或set访问器的类成员称为“属性”(英文Property),所以本文中我将使用“特性”这个名词,以区分“属性”(Property)。  
    中文版的VS2005使用“属性”。

1.1 System.ObsoleteAttribute 特性

我们通过这个例子来看一下特性是如何解决上面的问题:我们可以给旧的SendMsg()方法上面加上Obsolete特性来告诉编译器这个方法已经过时,然后当编译器发现当程序中有地方在使用这个用Obsolete标记过的方法时,就会给出一个警告信息。

namespace Attribute {

    public class Message {}
   
    public class TestClass {
       // 添加Obsolete特性
       [Obsolete("请使用新的SendMsg(Message msg)重载方法")]
       public static void ShowMsg() {
           Console.WriteLine("这是旧的SendMsg()方法");
       }

       public static void ShowMsg(Message msg) {
           Console.WriteLine("新SendMsg()方法");
       }

    }

    class Program {
       static void Main(string[] args) {
           TestClass.ShowMsg();
           TestClass.ShowMsg(new Message());         
       }
    }
}

现在运行这段代码,我们会发现编译器给出了一个警告:警告CS0618: “Attribute.TestClass.ShowMsg()”已过时:“请使用新的SendMsg(Message msg)重载方法”。通过使用特性,我们可以看到编译器给出了警告信息,告诉客户程序存在一个新的方法可供使用,这样,程序员在看到这个警告信息后,便会考虑使用新的SendMsg()方法。

NOTE:简单起见,TestClass类和 Program位于同一个程序集中,实际上它们可以离得很远。

1.2 特性的使用方法

通过上面的例子,我们已经大致看到特性的使用方法:首先是有一对方括号“[]”,在左方括号“[”后紧跟特性的名称,比如Obsolete,随后是一个圆括号“()”。和普通的类不同,这个圆括号不光可以写入构造函数的参数,还可以给类的属性赋值,在Obsolete的例子中,仅传递了构造函数参数。

 

NOTE:实际上,当你用鼠标框选住Obsolete,然后按下F12转到定义,会发现它的全名是ObsoleteAttribute,继承自Attribute类。但是这里却仅用Obsolete来标记方法,这是.Net的一个约定,所有的特性应该均以Attribute来结尾,在为对象标记特性时如果没有添加Attribute,编译器会自动寻找带有Attribute的版本。

 

NOTE使用构造函数参数,参数的顺序必须同构造函数声明时的顺序相同,所有在特性中也叫位置参数(Positional Parameters),与此相应,属性参数也叫做命名参数(Named Parameters)。

在下面会详细说明。

2.自定义特性(Custom Attributes)

2.1 范例介绍

      如果不能自己定义一个特性并使用它,我想你怎么也不能很好的理解特性,我们现在就自己构建一个特性。假设我们有这样一个很常见的需求:我们在创建或者更新一个类文件时,需要说明这个类是什么时候、由谁创建的,在以后的更新中还要说明在什么时候由谁更新的,可以记录也可以不记录更新的内容,以往你会怎么做呢?是不是像这样在类的上面给类添加注释:

//更新:Matthew, 2008-2-10, 修改 ToString()方法
//更新:Jimmy, 2008-1-18
//创建:张子阳, 2008-1-15
public class DemoClass{
    // Class Body
}

       这样的的确确是可以记录下来,但是如果有一天我们想将这些记录保存到数据库中作以备份呢?你是不是要一个一个地去查看源文件,找出这些注释,再一条条插入数据库中呢?

通过上面特性的定义,我们知道特性可以用于给类型添加元数据(描述数据的数据,包括数据是否被修改、何时创建、创建人,这些数据可以是一个类、方法、属性),这些元数据可以用于描述类型。那么在此处,特性应该会派上用场。那么在本例中,元数据应该是:注释类型(“更新”或者“创建”),修改人,日期,备注信息(可有可无)。而特性的目标类型是DemoClass类。

 

按照对于附加到DemoClass类上的元数据的理解,我们先创建一个封装了元数据的类RecordAttribute:

 

public class RecordAttribute {
    private string recordType;      // 记录类型:更新/创建
    private string author;          // 作者
    private DateTime date;          // 更新/创建 日期
    private string memo;         // 备注

    // 构造函数,构造函数的参数在特性中也称为“位置参数”。
    public RecordAttribute(string recordType, string author, string date) {
       this.recordType = recordType;
       this.author = author;
       this.date = Convert.ToDateTime(date);
    }

    // 对于位置参数,通常只提供get访问器
    public string RecordType {   get { return recordType; }   }
    public string Author { get { return author; } }
    public DateTime Date { get { return date; } }

    // 构建一个属性,在特性中也叫“命名参数”
    public string Memo {
       get { return memo; }
       set { memo = value; }
    }
}

 

 

NOTE:注意构造函数的参数 date,必须为一个常量、Type类型、或者是常量数组,所以不能直接传递DateTime类型。

这个类不光看上去,实际上也和普通的类没有任何区别,显然不能它因为名字后面跟了个Attribute就摇身一变成了特性。那么怎样才能让它称为特性并应用到一个类上面呢?进行下一步之前,我们看看.Net内置的特性Obsolete是如何定义的:

namespace System {
    [Serializable]
    [AttributeUsage(6140, Inherited = false)]
    [ComVisible(true)]
    public sealed class ObsoleteAttribute : Attribute {

       public ObsoleteAttribute();
       public ObsoleteAttribute(string message);
       public ObsoleteAttribute(string message, bool error);

       public bool IsError { get; }
       public string Message { get; }
    }
}

 

2.2 添加特性的格式(位置参数和命名参数)

      首先,我们应该发现,它继承自Attribute类,这说明我们的 RecordAttribute 也应该继承自Attribute类。 (一个特性类与普通类的区别是:继承了Attribute类)

       其次,我们发现在这个特性的定义上,又用了三个特性去描述它。这三个特性分别是:Serializable、AttributeUsage 和 ComVisible。Serializable特性我们前面已经讲述过,ComVisible简单来说是“控制程序集中个别托管类型、成员或所有类型对 COM 的可访问性”(微软给的定义)。这里我们应该注意到:特性本身就是用来描述数据的元数据,而这三个特性又用来描述特性,所以它们可以认为是“元数据的元数据”(元元数据:meta-metadata)。

从这里我们可以看出,特性类本身也可以用除自身以外的其它特性来描述,所以这个特性类的特性是元元数据。

 

因为我们需要使用“元元数据”去描述我们定义的特性 RecordAttribute,所以现在我们需要首先了解一下“元元数据”。这里应该记得“元元数据”也是一个特性,大多数情况下,我们只需要掌握 AttributeUsage就可以了,所以现在就研究一下它。我们首先看上面AttributeUsage是如何加载到ObsoleteAttribute特性上面的。

 

    [AttributeUsage(6140, Inherited = false)]

 

然后我们看一下AttributeUsage的定义:

namespace System {
    public sealed class AttributeUsageAttribute : Attribute {
       public AttributeUsageAttribute(AttributeTargets validOn);

       public bool AllowMultiple { get; set; }
       public bool Inherited { get; set; }
       public AttributeTargets ValidOn { get; }
    }
}

可以看到,它有一个构造函数,这个构造函数含有一个AttributeTargets类型的位置参数(Positional Parameter) validOn,还有两个命名参数(Named Parameter)。注意ValidOn属性不是一个命名参数,因为它不包含set访问器,(是位置参数)

 

这里大家一定疑惑为什么会这样划分参数,这和特性的使用是相关的。假如AttributeUsageAttribute 是一个普通的类,我们一定是这样使用的:

// 实例化一个 AttributeUsageAttribute 类


AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class);
usage.AllowMultiple = true;  // 设置AllowMutiple属性
usage.Inherited = false;// 设置Inherited属性

 

      但是,特性只写成一行代码,然后紧靠其所应用的类型(目标类型),那么怎么办呢?微软的软件工程师们就想到了这样的办法:不管是构造函数的参数 还是 属性,统统写到构造函数的圆括号中,对于构造函数的参数,必须按照构造函数参数的顺序和类型;对于属性,采用“属性=值”这样的格式,它们之间用逗号分隔。于是上面的代码就减缩成了这样:

[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]

 

      可以看出,AttributeTargets.Class是构造函数参数(位置参数),而AllowMutiple 和 Inherited实际上是属性(命名参数)。命名参数是可选的。将来我们的RecordAttribute的使用方式于此相同。(为什么管他们叫参数,我猜想是因为它们的使用方式看上去更像是方法的参数吧。)

 

假设现在我们的RecordAttribute已经OK了,则它的使用应该是这样的:

[RecordAttribute("创建","张子阳","2008-1-15",Memo="这个类仅供演示")]
public class DemoClass{
	// ClassBody
}

其中recordType, author 和 date 是位置参数,Memo是命名参数。

 

2.3 AttributeTargets 位标记

     从AttributeUsage特性的名称上就可以看出它用于描述特性的使用方式。具体来说,首先应该是其所标记的特性可以应用于哪些类型或者对象。从上面的代码,我们看到AttributeUsage特性的构造函数接受一个 AttributeTargets 类型的参数,那么我们现在就来了解一下AttributeTargets。

AttributeTargets 是一个位标记,它定义了特性可以应用的类型和对象。

[Flags]
public enum AttributeTargets {

    Assembly = 1,         //可以对程序集应用属性。
    Module = 2,              //可以对模块应用属性。
    Class = 4,            //可以对类应用属性。
    Struct = 8,              //可以对结构应用属性,即值类型。
    Enum = 16,            //可以对枚举应用属性。
    Constructor = 32,     //可以对构造函数应用属性。
    Method = 64,          //可以对方法应用属性。
    Property = 128,           //可以对属性 (Property) 应用属性 (Attribute)。
    Field = 256,          //可以对字段应用属性。
    Event = 512,          //可以对事件应用属性。
    Interface = 1024,            //可以对接口应用属性。
    Parameter = 2048,            //可以对参数应用属性。
    Delegate = 4096,             //可以对委托应用属性。
    ReturnValue = 8192,             //可以对返回值应用属性。
    GenericParameter = 16384,    //可以对泛型参数应用属性。
    All = 32767,  //可以对任何应用程序元素应用属性。
}

 

现在应该不难理解为什么上面我范例中用的是:

[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]

 

而ObsoleteAttribute特性上加载的 AttributeUsage是这样的:

 

[AttributeUsage(6140, Inherited = false)]

 

因为AttributeUsage是一个位标记,所以可以使用按位或“|”来进行组合。所以,当我们这样写时:

 

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)

 

意味着既可以将特性应用到类上,也可以应用到接口上。

 

NOTE:这里存在着两个特例:观察上面AttributeUsage的定义,说明特性还可以加载到程序集Assembly和模块Module上,而这两个属于我们的编译结果,在程序中并不存在这样的类型,我们该如何加载呢?可以使用这样的语法:[assembly:SomeAttribute(parameter list)],另外这条语句必须位于程序语句开始之前。

2.4 Inherited 和 AllowMutiple属性

AllowMutiple 属性用于设置该特性是不是可以重复地添加到一个类型上(默认为false),就好像这样:

[RecordAttribute("更新","Jimmy","2008-1-20")]
[RecordAttribute("创建","张子阳","2008-1-15",Memo="这个类仅供演示")]
public class DemoClass{
// ClassBody
}

所以,我们必须显示的将AllowMutiple设置为True。

Inherited 就更复杂一些了,假如有一个类继承自我们的DemoClass,那么当我们将RecordAttribute添加到DemoClass上时,DemoClass的子类也会获得该特性。而当特性应用于一个方法,如果继承自该类的子类将这个方法覆盖,那么Inherited则用于说明是否子类方法是否继承这个特性。

在我们的例子中,将 Inherited 设为false。

2.5 实现 RecordAttribute

现在实现RecordAttribute应该是非常容易了,对于类的主体不需要做任何的修改,我们只需要让它继承自Attribute基类,同时使用AttributeUsage特性标记一下它就可以了(假定我们希望可以对类和方法应用此特性):

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=true, Inherited=false)]
public class RecordAttribute:Attribute {
    // 略
}

2.6 使用 RecordAttribute

我们已经创建好了自己的自定义特性,现在是时候使用它了。

[Record("更新", "Matthew", "2008-1-20", Memo = "修改 ToString()方法")]
[Record("更新", "Jimmy", "2008-1-18")]
[Record("创建", "张子阳", "2008-1-15")]
public class DemoClass {     
    public override string ToString() {
       return "This is a demo class";
    }
}

class Program {
    static void Main(string[] args) {
       DemoClass demo = new DemoClass();
       Console.WriteLine(demo.ToString());
    }
}

 

这段程序简单地在屏幕上输出一个“This is a demo class”。我们的属性也好像使用“//”来注释一样对程序没有任何影响,实际上,我们添加的数据已经作为元数据添加到了程序集中。可以通过IL DASM看到:

3.使用反射查看自定义特性

      利用反射来查看 自定义特性信息 与 查看其他信息 类似,首先基于类型(本例中是DemoClass)获取一个Type对象,然后调用Type对象的GetCustomAttributes()方法,获取应用于该类型上的特性。当指定GetCustomAttributes(Type attributeType, bool inherit) 中的第一个参数attributeType时,将只返回指定类型的特性,否则将返回全部特性;第二个参数指定是否搜索该成员的继承链以查找这些属性。

class Program {
    static void Main(string[] args) {
       Type t = typeof(DemoClass);
       Console.WriteLine("下面列出应用于 {0} 的RecordAttribute属性:" , t);

       // 获取所有的RecordAttributes特性
       object[] records = t.GetCustomAttributes(typeof(RecordAttribute), false);

       foreach (RecordAttribute record in records) {
           Console.WriteLine("   {0}", record);
           Console.WriteLine("      类型:{0}", record.RecordType);
           Console.WriteLine("      作者:{0}", record.Author);
           Console.WriteLine("      日期:{0}", record.Date.ToShortDateString());
           if(!String.IsNullOrEmpty(record.Memo)){
              Console.WriteLine("      备注:{0}",record.Memo);
           }
       }
    }
}

 

输出为:

下面列出应用于 AttributeDemo.DemoClass 的RecordAttribute属性:
   AttributeDemo.RecordAttribute
      类型:更新
      作者:Matthew
      日期:2008-1-20
      备注:修改 ToString()方法
   AttributeDemo.RecordAttribute
      类型:更新
      作者:Jimmy
      日期:2008-1-18
   AttributeDemo.RecordAttribute
      类型:创建
      作者:张子阳
      日期:2008-1-15

好了,到了这一步,我想将这些数据录入数据库中将不再是个问题,我们关于反射自定义特性的章节也就到此为止了。

 

转载自http://www.cnblogs.com/discoverx/archive/2008/05/29/1210067.html

分享到:
评论

相关推荐

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

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

    反射获取自定义特性.zip

    Java和C#等面向对象的语言提供了反射机制,使开发者能够在运行时动态地创建对象、调用方法以及访问和修改类的属性。本文将深入探讨如何通过反射获取类的自定义特性,并创建和使用自定义特性类。 首先,我们需要理解...

    C# 自定义特性

    这通常涉及到继承自System.Attribute基类,并使用AttributeUsage属性来定义自定义特性的使用范围和限制。 以下是一些关于C#自定义特性的关键知识点: 1. **定义自定义特性**:自定义特性类需要继承自System....

    c# 自定义特性demo

    本示例“c# 自定义特性demo”将引导我们深入了解如何在C#中定义和使用自定义特性。 首先,让我们了解一下如何定义自定义特性。在C#中,自定义特性是通过继承`System.Attribute`基类来创建的。例如,我们可以创建一...

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

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

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

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

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

    Visual Studio 2017是开发C#应用程序的强大工具,它支持对特性(Attribute)的创建和使用,以及通过反射进行元数据的探索。 特性(Attribute)是C#中的一种特殊类,它们以`[ ]`括起来,用于标记程序元素。例如,`...

    c#自定义特性

    C#中的自定义特性(Custom Attributes)是一种非常强大的机制,它允许开发人员将元数据(即有关代码的信息)附加到程序的各个部分,如类、方法、属性等。通过这种方式,可以在运行时获取这些元数据,并据此作出相应...

    反射和自定义特性DEMO

    自定义特性的定义通常是一个继承自`System.Attribute`的类,而应用特性则使用`[YourCustomAttribute]`语法。例如,你可以定义一个`AuthorAttribute`来表示代码作者,然后在类或方法上使用它: ```csharp ...

    c#特性(Attribute)简单示例

    下面将详细讲解C#特性的基本概念、使用方法以及一些常见的内置特性。 一、特性基础 1. 定义特性:在C#中,特性是以`[AttributeName]`的形式存在的,它们通常是自定义的类,继承自`System.Attribute`基类。例如,...

    反射-自定义特性(例程)

    本文将深入探讨如何使用C#语言来创建自定义特性,并通过反射来获取这些特性的元数据值。这是一项关键技能,特别是在设计灵活、可扩展的框架或者需要在运行时动态调整行为的应用程序时。 首先,让我们了解什么是特性...

    C#2017实现自定义属性实现标签特性简单例子可执行

    本篇文章将深入探讨如何在C# 2017中创建和使用自定义属性,并结合WPF(Windows Presentation Foundation)的应用场景给出一个具体的实例。 首先,理解自定义属性的本质。自定义属性是继承自`System.Attribute`的类...

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

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

    C#2017实现自定义属性实现多个标签叠加特性简单例子可执行

    本示例将深入探讨如何在C# 2017中利用自定义属性来实现多个标签叠加特性。自定义属性是C#中的一种元数据,允许我们在代码中附加额外的信息,这些信息可以被编译器、运行时或其他工具用来执行特定的任务或提供扩展...

    C#基础--Attribute(标签) 和 reflect(反射) 应用

    在.NET框架中,C#是一种强大的面向对象的编程语言,其特性丰富,其中包括了Attribute(属性)和Reflect(反射)这两个重要概念。这两者在实际开发中有着广泛的应用,能够帮助程序员实现元数据的标记、运行时代码的...

    net c# 自定义特性并获取特性内的值

    总的来说,这个项目演示了如何在C#中创建自定义特性,以及如何通过反射获取和使用这些特性中的数据,是学习C#元编程和反射机制的一个实用案例。通过深入理解这部分内容,开发者可以更好地实现代码的自我描述,提高...

    C#自定义特性.pdf

    C#自定义特性Attribute C#中的自定义特性Attribute是将预定义的系统信息或用户定义的自定义信息与目标元素相关联的一种机制。目标元素可以是程序集、类、构造函数、委托、枚举、事件、字段、接口、方法、可移植可...

    winform 自定义特性源码

    在WinForm应用中,自定义特性(Custom Attributes)是一个非常实用的功能,允许我们为类、方法、属性等编程元素添加元数据,这些元数据可以在运行时被反射机制读取,从而实现各种高级功能。本资源“winform 自定义...

    C#特性完整版实例代码

    C#的特性分为几个层次,包括系统内置的特性、.NET框架提供的特性以及自定义特性。内置特性如`Obsolete`用于标记不再推荐使用的代码,`Conditional`控制条件编译等。框架特性如`DataContract`和`DataMember`用于数据...

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

    以上内容是对C#特性的详细回顾,包括特性的定义、使用方法、参数形式、目标类型、应用场景、创建自定义特性和通过反射访问特性等方面的知识。掌握这些知识点对于理解.NET框架以及进行高效的C#编程都是非常有帮助的。

Global site tag (gtag.js) - Google Analytics