C#图解教程 第二十四章 反射和特性

Note程序员

  • 类的元数据包含该类的成员和特性
  • 程序的元数据能够理解为程序的结构信息
  • 反射(reflection)用来查看元数据
  • C#中经过Type类来反射
  • 特性(attribute)用来给类型添加元数据

PS:理解有待增强数组

 

反射和特性

元数据和反射


大多数程序都要处理数据,包括读、写、操做和显示数据。(图形也是一种数据的形式。)然而,对于某些程序来讲,它们操做的数据不是数字、文本或图形,而是程序和程序类型自己的信息。浏览器

  • 有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中
  • 程序在运行时,能够查看其余程序集或其自己的元数据。一个运行的程序査看自己的元数据或其余程序的元数据的行为叫作反射(reflection)

对象浏览器是显式元数据的程序的一个示例。它能够读取程序集,而后显示所包含的类型以及类型的全部特性和成员。
本章将介绍程序如何使用Type类来反射数据,以及程序员如何使用特性来给类型添加元数据。安全

要使用反射,咱们必须使用System.Reflection命名空间。app

Type 类


以前已经介绍了如何声明和使用C#中的类型。包括预约义类型(int、long和string等)、BCL中的类型(Console、IEnumerable等)以及用户自定义类型(MyClass、Mydel等)。每一种类型都有本身的成员和特性。
BCL声明了一个叫作Type的抽象类,它被设计用来包含类型的特性。使用这个类的对象能让咱们获取程序使用的类型的信息。
因为Type是抽象类,所以它不能有实例。而是在运行时,CLR建立从Type(RuntimeType)派生的类的实例,Type包含了类型信息。当咱们要访问这些实例时,CLR不会返回派生类的引用而是Type基类的引用。可是,为了简单起见,在本章剩余的篇幅中,我会把引用所指向的对象称为Type类型的对象(虽然从技术角度来讲是一个BCL内部的派生类型的对象)。
须要了解的有关Type的重要事项以下:框架

  • 对于程序中用到的每个类型,CLR都会建立一个包含这个类型信息的Type类型的对象
  • 程序中用到的每个类型都会关联到独立的Type类的对象
  • 无论建立的类型有多少个实例,只有一个Type对象会关联到全部这些实例

下图显示了一个运行的程序,它有两个MyClass对象和一个OtherClass对象。注意,尽管有两个MyClass的实例,只会有一个Type对象来表示它。

咱们能够从Type对象中获取须要了解的有关类型的几乎全部信息。下表列出了类中更有用的成员。
函数

获取Type对象


本节学习使用GetType方法和typeof运算符来获取Type对象。object类型包含了一个叫作GetType的方法,它返回对实例的Type对象的引用。因为每个类型最终都是从object继承的,因此咱们能够在任何类型对象上使用GetType方法来获取它的Type对象,以下所示: 学习

Type t = myInstance.GetType(); 


下面的代码演示了如何声明一个基类以及从它派生的子类。Main方法建立了每个类的实例而且把这些引用放在了一个叫作bca的数组中以方便使用。在外层的foreach循环中,代码获得了Type对象而且输出类的名字,而后获取类的字段并输出。下图演示了内存中的对象。
spa

using System;
using System.Reflection;
class BaseClass
{
    public int BaseField=0;
}
class DerivedClass:BaseClass
{
    public int DerivedField=0;
}
class Program
{
    static void Main()
    {
        var bc=new BaseClass();
        var dc=new DerivedClass();
        BaseClass[] bca=new BaseClass[]{bc,dc};

        foreach(var v in bca)
        {
            Type t=v.GetType();

            Console.WriteLine("Object type : {0}",t.Name);

            FieldInfo[] fi=t.GetFields();
            foreach(var f in fi)
            {
                Console.WriteLine("    Field : {0}",f.Name);
            }
            Console.WriteLine();
        }
    }
}


咱们还可使用typeof运算符来获取Type对象。只须要提供类型名做为操做数,它就会返回Type对象的引用,以下所示:设计

Type t = typeof(DerivedClass);
            ↑        ↑
         运算符 但愿的Type对象的类型

下面的代码给出了一个使用typeof运算符的简单示例:

using System;
using System.Reflection;

namespace SimpleReflection
{
    class BaseClass
    {
        public int MyFieldBase;
    }
    class DerivedClass:BaseClass
    {
        public int MyFieldDerived;
    }
    class Program
    {
        static void Main()
        {
            Type tbc=typeof(DerivedClass);
            Console.WriteLine("Result is {0}.",tbc.Name);

            Console.WriteLine("It has the following fields:");
            FieldInfo[] fi=tbc.GetFields();
            foreach(var f in fi)
            {
                Console.WriteLine("    {0}",f.Name);
            }
        }
    }
}

什么是特性

特性(attribute)是一种容许咱们向程序的程序集增长元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。

  • 将应用了特性的程序结构(program construct)叫作目标(target)
  • 设计用来获取和使用元数据的程序(好比对象浏览器)叫作特性的消费者(consumer)
  • .NET预约了不少特性,咱们也能够声明自定义特性

下图是使用特性中相关组件的概览,而且也演示了以下有关特性的要点。

  • 咱们在源代码中将特性应用于程序结构
  • 编译器获取源代码而且从特性产生元数据,而后把元数据放到程序集中
  • 消费者程序能够获取特性的元数据以及程序中其余组件的元数据。注意,编译器同时生产和消费特性

根据惯例,特性名使用Pascal命名法而且以Attribute后缀结尾。当为目标应用特性时,咱们能够不使用后缀。例如,对于SerializableAttribute和MyAttributeAttribute这两个特性,咱们在把它们应用到结构时可使用Serializable和MyAttribute短名称。

应用特性


咱们先不讲解如何建立特性,而是看看如何使用已定义的特性。这样,你会对它们的使用状况有个大体了解。
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。咱们能够经过把特性应用到结构来实现。

  • 在结构前放置特性片断来应用特性
  • 特性片断被方括号包围,其中是特性名和特性的参数列表

例如,下面的代码演示了两个类的开始部分。最初的几行代码演示了把一个叫作Serializable的特性应用到MyClass。注意,Serializable没有参数列表。第二个类的声明有一个叫作MyAttribute的特性,它有一个带有两个string参数的参数列表。

[Serializable]
public class MyClass
{
    …
}
[MyAttribute("Simple class","Version 3.57")]
public class MyOtherClass
{
    …
}

有关特性须要了解的重要事项以下:

  • 大多数特性只针对直接跟随在一个或多个特性片断后的结构
  • 应用了特性的结构称为被特件装饰(decorated或adorned,二者都应用得很广泛)

预约义的保留的特性


在学习如何定义本身的特性以前,本小节会先介绍几个.NET预约义特性。

Obsolete(废弃)特性

一个程序可能在其生命周期中经历屡次发布,并且极可能延续多年。在程序生命周期的后半部分,程序员常常须要编写相似功能的新方法替换老方法。出于多种缘由,你可能不想再使用那些调用过期的旧方法的老代码,而只想用新编写的代码调用新方法。
若是出现这种状况,你确定但愿稍后操做代码的团队成员或程序员也只使用新代码。要警告他们不要使用旧方法,可使用Obsolete特性将程序结构标注为过时的,而且在代码编译时显式有用的警告消息。如下代码给出了一个使用的示例:

class Program
{
    //应用特性
    [Obsolete("User method SuperPrintOut")]
    static void PrintOut(string str)
    {
        Console.WriteLine(str);
    }
    static void Main(string[] args)
    {
        PrintOut("Start of Main");
    }
}

注意,即便PrintOut被标注为过时,Main方法仍是调用了它。代码编译也运行得很好而且产生了以下的输出:

不过,在编译的过程当中,编译器产生了下面的CS0618警告消息来通知咱们正在使用一个过时的结构:

另一个Obsolete特性的重载接受了bool类型的第二个参数。这个参数指定目标是否应该被标记为错误而不只仅是瞥告。如下代码指定了它须要被标记为错误:

                                   标记为错误
                                       ↓
[Obsolete("User method SuperPrintOut",true)]
static void PrintOut(string str)
{
    …
}

Conditional特性

Note
Conditional特性相似于C语言的条件编译

Conditional特性容许咱们包括或排斥特定方法的全部调用。为方法声明应用Conditional特性并把编译符做为参数来使用。

  • 若是定义了编译符号,那么编译器会包含全部调用这个方法的代码,这和普通方法没有什么区别
  • 若是没有定义编译符号,那么编译器会忽略代码中这个方法的全部调用

定义方法的CIL代码自己老是会包含在程序集中。只是调用代码会被插入或忽略。
例如,在以下的代码中,把Conditional特性应用到对一个叫作TraceMessage的方法的声明上。特性只有一个参数,在这里是字符串DoTrace。

  • 当编译器编译这段代码时,它会检査是否有一个编译符号被定义为DoTrace
  • 若是DoTrace被定义,编译器就会像往常同样包含全部对TraceMessage方法的调用
  • 若是没有DoTrace这样的编译符号被定义,编译器就不会输出任何对TraceMessage的调用
[Conditional("DoTrace")]
static void TraceMessage(string str)
{
    Console.WriteLine(str);
}

Conditional特性的示例
如下代码演示了一个使用Conditional特性的完整示例。

  • Main方法包含了两个对TraceMessage方法的调用
  • TraceMessage方法的声明被用Conditional特性装饰,它带有DoTrace编译符号做为参数。所以,若是DoTrace被定义,那么编译器就会包舍全部对TraceMessage的调用代码
  • 因为代码的第一行定义了叫作DoTrace的编译符,编译器会包含两个对TraceMessage的调用
#define DoTrace
using System;
using System.Diagnostics;
namespace AttributeConditional
{
    class Program
    {
        [Conditional("DoTrace")]
        static void TraceMessage(string str)
        {
            Console.WriteLine(str);
        }
        static void Main()
        {
            TraceMessage("Start of Main");
            Console.WriteLine("Doing work in Main.");
            TraceMessage("End of Main");
        }
    }
}


若是注释掉第一行来取消DoTrace的定义,编译器就再也不会插人两次对TraceMessage的调用代码。此次,若是咱们运行程序,就会产生以下输出:

调用者信息特性

调用者信息特性能够访问文件路径、代码行数、调用成员的名称等源代码信息。

  • 这三个特性名称为CallerFilePathCallerLineNumberCallerMemberName
  • 这些特性只能用于方法中的可选参数

下面的代码声明了一个名为MyTrace的方法,它在三个可选参数上使用了这三个调用者信息特性。若是调用方法时显式指定了这些参数,则会使用真正的参数值。但在下面所示的Main方法中调用时,没有显式提供这些值,所以系统将会提供源代码的文件路径、调用该方法的代码行数和调用该方法的成员名称。

using System;
using System.Runtime.CompilerServices;

public static class Program
{
    public static void MyTrace(string message,
                                [CallerFilePath] string fileName="",
                                [CallerLineNumber] int lineNumber=0,
                                [CallerMemberName] string callingMember="")
    {
        Console.WriteLine("File:         {0}",fileName);
        Console.WriteLine("Line:         {0}",lineNumber);
        Console.WriteLine("Called From:  {0}",callingMember);
        Console.WriteLine("Message:      {0}",message);
    }
    public static void Main()
    {
        MyTrace("Simple message");
    }
}

DebuggerStepThrough 特性

咱们在单步调试代码时,经常但愿调试器不要进入某些方法。咱们只想执行该方法,而后继续调试下一行。DebuggerStepThrough特性告诉调试器在执行目标代码时不要进入该方法调试。
在我本身的代码中,这是最常使用的特性。有些方法很小而且毫无疑问是正确的,在调试时对其反复单步调试只能徒增烦恼。但使用该特性时要十分当心,由于你并不想排除那些可能含有bug的代码。
关于DebuggerStepThrough要注意如下两点:

  • 该特性位于System.Diagnostics命名空间
  • 该特性可用于类、结构、构造函数、方法或访问器

下面这段随手编造的代码在一个访问器和一个方法上使用了该特性。你会发现,调试器调试这段代码时不会进入IncrementFields方法或X属性的set访问器。

using System;
using System.Diagnostics;

class Program
{
    int _x=1;
    int X
    {
        get{return _x;}
        [DebuggerStepThrough]
        set
        {
            _x=_x*2;
            _x+=value;
        }
    }

    public int Y{get;set;}

    public static void Main()
    {
        var p=new Program();
        p.IncrementFields();
        p.X=5;
        Console.WriteLine("X = {0}, Y = {1}",p.X,p.Y);
    }
    [DebuggerStepThrough]
    void IncrementFields()
    {
        X++;
        Y++;
    }
}

其余预约义特性

.NET框架预约义了不少编译器和CLR能理解和解释的特性,下表列出了一些。在表中使用了不带Attribute后缀的短名称。例如,CLSCompliant的全名是CLSCompliantAttribute。

有关应用特性的更多内容


至此,咱们演示了特性的简单使用,都是为方法应用单个特性。这部份内容将会讲述其余特性的使用方式。

多个特性

咱们能够为单个结构应用多个特性。

  • 多个特性可使用下面列出的任何一种格式:
    • 独立的特性片断相互叠在一块儿
    • 单个特性片断,特性之间使用逗号分隔
  • 咱们能够以任何次序列出特性

例如,下面的两个代码片断显示了应用多个特性的两种方式。两个片断的代码是等价的。

[Serializable ]                       //多层结构
[MyAttribute("Simple class", "Version 3.57")]

[MyAttribute("Simple class", "Version 3.57"),Serializable]    //逗号分隔

其余类型的目标

除了类,咱们还能够将特性应用到诸如字段和属性等其余程序结构。如下的声明显示了字段上的特性以及方法上的多个特性:

[MyAttribute("Holds a value", "Version 3.2")]    //字段上的特性
public int MyField;

[Obsolete]                                       //方法上的特性
[MyAttribute("Prints out a message.", "Version 3.6")]
public void Printout()
{
    …
}

咱们还能够显式地标注特性,从而将它应用到特殊的目标结构。要使用显式目标,在特性片断的开始处放置目标类型,后面跟冒号。例如,以下的代码用特性装饰方法,而且还把特性应用到返回值上。

显式目标说明符
   ↓
[method: MyAttribute("Prints out a message.", "Version 3.6")]
[return: MyAttribute("This value represents …", "Version 2.3")]
public long ReturnSetting()
{
    …
}

以下表所列,C#语言定义了10个标准的特性目标。大多数目标名能够自明(self-explanatory),而type覆盖了类、结构、委托、枚举和接口。 typevar目标名称指定使用泛型结构的类型参数。

全局特性

咱们还能够经过使用assembly和module目标名称来使用显式目标说明符把特性设置在程序集或模块级別。(程序集和模块在第21章中解释过。)一些有关程序集级别的特性的要点以下:

  • 程序级级别的特性必须放置在任何命名空间以外,而且一般放置在AssemblyInfo.cs文件中
  • AssemblyInfo.cs文件一般包含有关公司、产品以及版权信息的元数据

以下的代码行摘自AssemblyInfo.cs文件:

[assembly: AssemblyTitle("SuperWidget")]
[assembly: AssemblyDescription("Implements the SuperWidget product.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("McArthur Widgets, Inc.")]
[assembly: AssemblyProduct("Super Widget Deluxe")]
[assembly: AssemblyCopyright("Copyright © McArthur Widgets 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

自定义特性


你或许已经注意到了,应用特性的语法和以前见过的其余语法很不相同。你可能会以为特性是和结构彻底不一样的类型,其实不是,特性只是某个特殊类型的类。
有关特性类的一些要点以下。

  • 用户自定义的特性类叫作自定义特性
  • 全部特性类都派生自System.Attribute

声明自定义特性

整体来讲,声明一个特性类和声明其余类同样。然而,有一些事项值得注意,以下所示。

  • 要声明一个自定义特性,须要作以下工做
    • 声明一个派生自System.Attribute的类
    • 给它起一个之后缀Attribute结尾的名字
  • 安全起见,一般建议你声明一个sealed的特性类(sealed密封类,不能被继承)

例如,下面的代码显示了MyAttributeAttribute特性的声明的开始部分:

                         特性名                 基类
                           ↓                     ↓
public sealed class MyAttributeAttribute : System.Attribute
{
…
}

因为特性持有目标的信息,全部特性类的公共成员只能是:

  • 字段
  • 属性
  • 构造函数

使用特性的构造函数

特性和其余类同样,都有构造函数。每个特性至少必须有一个公共构造函数。

  • 和其余类同样,若是你不声明构造函数,编译器会为咱们产生一个隐式、公共且无参的构造函数
  • 特性的构造函数和其余构造函数同样,能够被重载
  • 声明构造函数时必须使用类全名,包括后缀。咱们只能够在应用特性时使用短名称

例如,若是有以下的构造函数(名字没有包含后缀),编译器会产生一个错误消息:

public MyAttributeAttribute(string desc,string ver)
{
    Description=desc;
    VersionNumber=ver;
}

指定构造函数

当咱们为目标应用特性时,实际上是在指定应该使用哪一个构造函数来建立特性的实例。列在特性应用中的参数其实就是构造函数的参数。
例如,在下面的代码中,MyAttribute被应用到一个字段和一个方法上。对于字段,声明指定了使用单个字符串的构造函数。对于方法,声明指定了使用两个字符串的构造函数。

[MyAttribute("Holds a value")]    //使用一个字符串的构造函数
public int MyField;

[MyAttribute("version 1.3", "Sal Martin")]    //使用两个字符串的构造函数
public void MyMethod()
{
    …
}

其余有关特性构造函数的要点以下。

  • 在应用特性时,构造函数的实参必须是在编译期能肯定值的常量表达式
  • 若是应用的特性构造函数没有参数,能够省略圆括号。例如,以下代码的两个类都使用MyAttr特性的无参构造函数。两种形式的意义是相同的
[MyAttr]
class SomeClass …

[MyAttr()]
class OtherClass …

使用构造函数

和其余类同样,咱们不能显式调用构造函数。特性的实例建立后,只有特性的消费者访问特性时才能调用构造函数。这一点与其余类的实例很不相同,这些实例都建立在使用对象建立表达式的位置。应用一个特性是一条声明语句,它不会决定何时构造特性类的对象。
下图比较了普通类构造函数的使用和特性的构造函数的使用。

  • 命令语句的实际意义是:"在这里建立新的类"
  • 声明语句的意义是:"这个特性和这个目标相关联,若是须要构造特性,使用这个构造函数"

构造函数中的位置参数和命名参数

和普通类的方法与构造方法类似,特性的构造方法一样可使用位置参数和命名参数。以下代码显示了使用一个位置参数和两个命名参数来应用一个特性:

                   位置参数            命名参数              命名参数
                      ↓                   ↓                   ↓
[MyAttribute("An excellent class",Reviewer="Amy McArthur",Ver="0.7.15.33")]

下面的代码演示了特性类的声明以及为MyClass类应用特性。注意,构造函数的声明只列出了一个形参,但咱们可经过命名参数给构造函数3个实参。两个命名参数设置了字段Ver和Reviewer的值。

public sealed class MyAttributeAttribute : System.Attribute
{
    public string Description;
    public string Ver;
    public string Reviewer;

    public MyAttributeAttribute(string desc) //一个形参
    {
        Description = desc;
    }
}
             //三个实参
[MyAttribute("An excellent class”, Reviewer="Amy McArthur", Ver="7.15.33")]
class MyClass
{
    …
}

构造函教须要的任何位置参数都必须放在命名参数以前。

限制特性的使用

咱们已经看到了能够为类应用特性。而特性自己就是类,有一个很重要的预约义特性能够用来应用到自定义特性上,那就是AttributeUsage特性。咱们可使用它来限制特性使用在某个目标类型上。
例如,若是咱们但愿自定义特性MyAttribute只能应用到方法上,那么能够以以下形式使用AttributeUsage:

                   只针对方法
                       ↓
[AttributeUsage( AttributeTarget.Method )]
public sealed class MyAttributeAttribute : System.Attribute
{
    …
}

AttributeUsage有三个重要的公共属性,以下表所示。表中显示了属性名和属性的含义。对于后两个属性,还显示了它们的默认值。

AttributeUsage的构造函数
AttributeUsage的构造函数接受单个位置参数,该参数指定了特性容许的目标类型。它用这个参数来设置ValidOn属件,可接受目标类型是AttributeTarget枚举的成员。AttributeTarget枚举的完整成员列表以下表所示。
咱们能够经过使用按位或运算符来组合使用类型。例如,在下面的代码中,被装饰的特性只能应用到方法和构造函数上。

                                     目标
                                       ↓
[AttributeUsage( AttributeTarget.Method| AttributeTarget.Constructor )]
public sealed class MyAttributeAttribute : System.Attribute


当咱们为特性声明应用AttributeUsage时,构造函数至少须要一个参数,参数包含的目标类型会保存在ValidOn中。咱们还能够经过使用命名参数有选择性地设置Inherited和AllowMultiple属性。若是咱们不设置,它们会保持如表24-4所示的默认值。
做为示例,下面一段代码指定了MyAttribute的以下方面。

  • MyAttribute能且只能应用到类上
  • MyAttribute不会被应用它的派生类所继承
  • 不能有MyAttribute的多个实例应用到同一个目标上
[AttributeUsage( AttributeTarget.Class,     //必需的,位置参数
                Inherited = false,          //可选的,命名参数
                AllowMultiple = false )]    //可选的,命名参数
public sealed class MyAttributeAttribute : System.Attribute
{
    …
}

自定义特性的最佳实践

强烈推荐编写自定义特性时参考以下实践。

  • 特性类应该表示目标结构的一些状态
  • 若是特性须要某些字段,能够经过包含具备位置参数的构造函数来收集数据,可选字段能够采用命名参数按需初始化
  • 除了属性以外,不要实现公共方法或其余函数成员
  • 为了更安全,把特性类声明为sealed
  • 在特性声明中使用AttributeUsage来显式指定特性目标组

以下代码演示了这些准则:

[AttributeUsage( AttributeTargets.Class )]
public sealed class ReviewCommentAttribute : System.Attribute
{
    public    string    Description     {get;set;}
    public    string    VersionNumber   {get;set;}
    public    string    ReviewerID      {get;set;}

    public ReviewCommentAttribute(string desc, string ver)
    {
        Description = desc;
        VersionNumber = ver;
    }
}

访问特性


在本章开始处,咱们已经看到了可使用Type对象来获取类型信息。对于访问自定义特性来讲,咱们也能够这么作。Type的两个方法(IsDefined和GetCustomAttributes)在这里很是有用。

使用IsDefined方法

咱们可使用Type对象的IsDefined方法来检测某个特性是否应用到了某个类上。
例如,如下的代码声明了一个有特性的类MyClass,而且做为本身特性的消费者在程序中访问声明和被应用的特性。代码的开始处是MyAttribute特性和应用特性的MyClass类的声明。这段代码作了下面的事情。

  • 首先,Main建立了类的一个对象。而后经过使用从object基类继承的GetType方法获取了Type对象的一个引用
  • 有了Type对象的引用,就能够调用IsDefined方法来判断ReviewComment特性是否应用到了这个类
    • 第一个参数接受须要检査的特性的Type对象
    • 第二个参数是bool类型的,它指示是否搜索MyClass的继承树来查找这个特性
[AttributeUsage(AttributeTargets.Class)]
public sealed class ReviewCommentAttribute:System.Attribute
{…}

[ReviewComment("Check it out","2.4")]
class MyClass{}

class Program
{
    static void Main()
    {
        var mc=new MyClass();
        Type t=mc.GetType();
        bool isDefined=
            t.IsDefined(typeof(ReviewCommentAttribute),false);
        if(isDefined)
            Console.WriteLine("ReviewComment is applied to type {0}",t.Name);
    }
}

使用GetCustomAttributes方法

GetCustomAttributes方法返回应用到结构的特性的数组。

  • 实际返冋的对象是object的数组,所以咱们必须将它强制转换为相应的特性类型
  • 布尔参数指定是否搜索继承树来査找特性
  • object[] AttArr = t.GetCustomAttributes(false);
  • 调用GetCustomAttributes方法后,每个与目标相关联的特性的实例就会被建立

下面的代码使用了前面的示例中相同的特性和类声明。可是,在这种状况下,它不检测特性是否应用到了类,而是获取应用到类的特性的数组,而后遍历它们,输出它们的成员的值。

using System;

[AttributeUsage(AttributeTargets.Class)]
public sealed class MyAttributeAttribute:System.Attribute
{
    public  string  Description  {get;set;}
    public  string  VersionNumber{get;set;}
    public  string  ReviewerID   {get;set;}

    public MyAttributeAttribute(string desc,string ver)
    {
        Description=desc;
        VersionNumber=ver;
    }
}

[MyAttribute("Check it out","2.4")]
class MyClass
{
}
class Program
{
    static void Main()
    {
        Type t=typeof(MyClass);
        object[] AttArr=t.GetCustomAttributes(false);

        foreach(Attribute a in AttArr)
        {
            var attr=a as MyAttributeAttribute;
            if(null!=attr)
            {
                Console.WriteLine("Description    :{0}",attr.Description);
                Console.WriteLine("Version Number :{0}",attr.VersionNumber);
                Console.WriteLine("Reviewer ID    :{0}",attr.ReviewerID);
            }
        }
    }
}

相关文章
相关标签/搜索