.Net 中的反射

我在后面将较详细地介绍 Type类,现在只希望你能对反射有个第一印象,所以只简略地作以说明:Type抽象类提供了访问类型元数据的能力,当你实例化了一个Type对象后,你可以通过它的属性和方法,获取类型的元数据信息,或者进一步获得该类型的成员的元数据。注意到这里,因为Type对象总是基于某一类型的,并且它是一个抽象类,所以我们在创建Type类型时,必须要提供 类型,或者类型的实例,或者类型的字符串值。

我们看一看完整的代码:

private static DataTable GetDataTable() {
     Type enumType = typeof(BookingStatus);    // 创建类型
     FieldInfo[] enumFields = enumType.GetFields();    //获取字段信息对象集合
     DataTable table = new DataTable();
     table.Columns.Add("Name", Type.GetType("System.String"));
     table.Columns.Add("Value", Type.GetType("System.Int32"));
    // 遍历集合
     foreach (FieldInfo field in enumFields) {
        if (!field.IsSpecialName) {
            DataRow row = table.NewRow();
            row[0] = field.Name;
            row[1] = Convert.ToInt32(field.GetRawConstantValue());
            //row[1] = (int)Enum.Parse(enumType, field.Name); //也可以这样
            table.Rows.Add(row);
        }
     }
     return table;
 }

使用泛型来达到代码重用

观察上面的代码,如果我们现在有另一个枚举,叫做TicketStatus,那么我们要将它绑定到列表,我们唯一需要改动的就是这里:

Type enumType = typeof(BookingStatus); //将BookingStatus改作TicketStatus

既然这样,我们何不定义一个泛型类来进行代码重用呢?我们管这个泛型类叫做EnumManager<TEnum>

public static class EnumManager<TEnum>
{
    private static DataTable GetDataTable()
    {
       Type enumType = typeof(TEnum);  // 获取类型对象
       FieldInfo[] enumFields = enumType.GetFields();


       DataTable table = new DataTable();
       table.Columns.Add("Name", Type.GetType("System.String"));
       table.Columns.Add("Value", Type.GetType("System.Int32"));
       //遍历集合
       foreach (FieldInfo field in enumFields)
       {
           if (!field.IsSpecialName)
           {
               DataRow row = table.NewRow();
              row[0] = field.Name;
              row[1] = Convert.ToInt32(field.GetRawConstantValue());
              //row[1] = (int)Enum.Parse(enumType, field.Name); 也可以这样

              table.Rows.Add(row);
           }
       }
       return table;
    }

    public static void SetListControl(ListControl list)
    {
       list.DataSource = GetDataTable();
       list.DataTextField = "Name";
       list.DataValueField = "Value";
       list.DataBind();
    }
}

OK,现在一切都变得简便的多,以后,我们再需要将枚举绑定到列表,只要这样就行了(ddl开头的是DropDownList,rbl开头的是RadioButtonList):

EnumManager<BookingStauts>.SetListControl(ddlBookingStatus);
EnumManager<TicketStatus>.SetListControl(rblTicketStatus);

反射特性(Attribute):自定义特性(Custom Attributes)

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

然后我们看一下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; }
    }
}
AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class);
usage.AllowMultiple = true;  // 设置AllowMutiple属性
usage.Inherited = false;// 设置Inherited属性
public class DemoClass{
// ClassBody
}

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

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

// 实例化一个 AttributeUsageAttribute 类

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

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

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

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

[RecordAttribute("创建","张子阳","2008-1-15",Memo="这个类仅供演示")]

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

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)

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

使用反射查看自定义特性

利用反射来查看 自定义特性信息 与 查看其他信息 类似,首先基于类型(本例中是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);
           }
       }
    }
}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值