• 反射学习笔记一(转载)


    此时的问题和上面的类似:我们需要手动输入字符串“已过期”,此时Vs2005 的智能提示发挥不了任何作用,如果我们不幸将状态值记错,或者手误打错,就将导致程序错误,较为稳妥的做法还是按下F12导向到BookingStatus数组的定义,然后将“已过期”复制过来。

    现在,我们再看看如何来绑定到一个DropDownList下拉列表控件(Id为ddlStatus)上。

    ddlStatus.DataSource = BookingStatus;
    ddlStatus.DataBind();

    但是我们发现产生的HTML代码是这样:

    <select name="ddlStatus" id="ddlStatus">
        <option value="未提交">未提交</option>
        <option value="已提交">已提交</option>
        <option value="已取消">已取消</option>
        <option value="受理中">受理中</option>
        <option value="已退回">已退回</option>
        <option value="已订妥">已订妥</option>
        <option value="已过期">已过期</option>
    </select>

    我们看到,列表项的value值与text值相同,这显然不是我们想要的,怎么办呢?我们可以给下拉列表写一个数据绑定的事件处理方法。

    protected void Page_Load(object sender, EventArgs e) {   
        ddlStatus.DataSource = BookingStatus;
        ddlStatus.DataBound += new EventHandler(ddlStatus_DataBound);
        ddlStatus.DataBind();
    }

    void ddlStatus_DataBound(object sender, EventArgs e) {
        int i = 0;
        ListControl list = (ListControl)sender; //注意,将sender转换成ListControl
        foreach (ListItem item in list.Items) {
           i++;
           item.Value = i.ToString();         
        }
    }

    这样,我们使用数组完成了我们期望的效果,虽然这样实现显得有点麻烦,虽然还存在上面提到的不便于使用的问题,但这些问题我们耐心细心一点就能克服,而软件开发几乎从来就没有100%完美的解决方案,那我们干脆就这样好了。

    NOTE:在ddlStatus_DataBound事件中,引发事件的对象sender显然是DropDownList,但是这里却没有将sender转换成DropDownList,而是将它转换成基类型ListControl。这样做是为了更好地进行代码重用,ddlStatus_DataBound事件处理方法将不仅限于 DropDownList,对于继承自ListControl的其他控件,比如RadioButtonList、ListBox也可以不加改动地使用ddlStatus_DataBound方法。

    这里也可以使用Dictionary<String, Int>来完成,但都存在类似的问题,就不再举例了。

     

    3.枚举及其问题

    然而不幸的事又发生了... 我们的预订程序分为两部分:一部分为B/S端,在B/S端可以进行酒店订单的 创建(未提交)、提交(已提交)、取消提交(已取消),另外还可以看到是不是已订妥;一部分为C/S端,为酒店的预订中心,它可以进行其他状态的操作。

    此时,对于整个系统来说,应该有全部的7个状态。但对于B/S端来说,它只有 未提交、已提交、已取消、已订妥 四个状态,对应的值分别为 1、2、3、6。

    我们回想一下上面是如何使用数组来解决的,它存在一个缺陷:我们默认地将订单状态值与数组的索引一一对应地联系了起来。

    所以在绑定DropDownList时,我们采用自增的方式来设定列表项的Value值;或者在显示状态时,我们通过lbStatus.Text = BookingStatus[myOrder.StatusId]; 这样的语句来完成。而当这种对应关系被打破时,使用数组的方法就失效了,因为如果不利用数组索引,我们没有额外的地方去存储状态的数字值。

    此时,我们想到了使用枚举:

    public enum BookingStatus {
        未提交 = 1,
        已提交,
        已取消,
        已订妥 = 6
    }

    我们想在页面输出一个订单的状态时,可以这样:

    HotelOrder myOrder = GetItem(orderId);         //获取一个订单对象
    lbStatus.Text = ((BookingStatus)myOrder.StatusId).ToString(); // 输出文本值

    我们想更新订单的状态为 “已提交”:

    myOrder.StatusId = (int)BookingStatus.已提交;

    当状态为“已取消”时我们想执行某个操作:

    if(BookingStatus.已取消 == (BookingStatus)myOrder.StatusId){
        // Do some action
    }

    此时,VS 2005 的智能提示已经可以发挥完全作用,当我们在BookingStatus后按下“.”时,可以显示出所有的状态值。

    NOTE:当我们使用枚举存储状态时,myOrder对象的StatusId最好为BookingStatus枚举类型,而非int类型,这样操作会更加便捷一些,但为了和前面使用数组时的情况保持统一,这里StatusId仍使用int类型。

    以上三种情况使用枚举都显得非常的流畅,直到我们需要绑定枚举到DropDownList下拉列表的时候:我们知道,可以绑定到下拉列表的有两类对象,一类是实现了IEnumerable接口的可枚举集合,比如ArrayList,String[],List<T>;一类是实现了IListSource的数据源,比如DataTable,DataSet。

    NOTE:实际上IListSource接口的GetList()方法返回一个IList接口,IList接口又继承了IEnumerable接口。由此看来,IEnumerable是实现可枚举集合的基础,在我翻译的一篇文章 C#中的枚举器 中,对这个主题做了详细的讨论。

    可我们都知道:枚举enum是一个基本类型,它不会实现任何的接口,那么我们下来该如何做呢?

     

    使用反射遍历枚举字段

    最笨也是最简单的办法,我们可以先创建一个GetDataTable方法,此方法依据枚举的字段值和数字值构建一个DataTable,最后返回这个构建好的DataTable:

      private static DataTable GetDataTable() {
         DataTable table = new DataTable();
         table.Columns.Add("Name", Type.GetType("System.String"));       //创建列
         table.Columns.Add("Value", Type.GetType("System.Int32"));       //创建列

         DataRow row = table.NewRow();
         row[0] = BookingStatus.未提交.ToString();
         row[1] = 1;
         table.Rows.Add(row);

         row = table.NewRow();
         row[0] = BookingStatus.已提交.ToString();
         row[1] = 2;
         table.Rows.Add(row);

         row = table.NewRow();
         row[0] = BookingStatus.已取消.ToString();
         row[1] = 3;
         table.Rows.Add(row);

         row = table.NewRow();
         row[0] = BookingStatus.已订妥.ToString();
         row[1] = 6;
         table.Rows.Add(row);

         return table;
     }

    接下来,为了方便使用,我们再创建一个专门采用这个DataTable来设置列表控件的方法SetListCountrol():

    // 设置列表
     public static void SetListControl(ListControl list) {
         list.DataSource = GetDataTable();      // 获取DataTable
         list.DataTextField = "Name";
         list.DataValueField = "Value";
         list.DataBind();
     }

    现在,我们就可以在页面中这样去将枚举绑定到列表控件:

    protected void Page_Load(object sender, EventArgs e)
    {
        SetListControl(ddlStatus);   // 假设页面中已有ID为ddlStatus 的DropDownList
    }

    如果所有的枚举都要通过这样去绑定到列表,我觉得还不如在数据库中直接建表,这样实在是太麻烦了,而且我们是根据枚举的文本和值去HardCoding出一个DataTable的:

    DataRow row = table.NewRow();
    row[0] = BookingStatus.未提交.ToString();
    row[1] = 1;
    table.Rows.Add(row);

    row = table.NewRow();
    row[0] = BookingStatus.已提交.ToString();
    row[1] = 2;
    table.Rows.Add(row);

    row = table.NewRow();
    row[0] = BookingStatus.已取消.ToString();
    row[1] = 3;
    table.Rows.Add(row);

    row = table.NewRow();
    row[0] = BookingStatus.已订妥.ToString();
    row[1] = 6;
    table.Rows.Add(row);

    这个时候,我们想有没有办法通过遍历来实现这里?如果想要遍历这里,首先,我们需要一个包含枚举的每个字段信息的对象,这个对象至少包含两条信息,一个是字段的文本(比如“未提交”),一个是字段的数字型值(比如1),我们暂且管这个对象叫做field。其次,应该存在一个可遍历的、包含了字段信息的对象(也就是filed) 的集合,我们暂且管这个集合叫做enumFields。

    那么,上面就可以这样去实现:

    foreach (xxxx field in enumFields)
    {
        DataRow row = table.NewRow();
        row[0] = field.Name;         // 杜撰的属性,代表 文本值(比如“未提交”)
        row[1] = filed.intValue;     // 杜撰的属性,代表 数字值(比如1)

        table.Rows.Add(row);
    }

    这段代码很不完整,我们注意到 xxxx,它应该是封装了字段信息(或者叫元数据metadata)的对象的类型。而对于enumFields,它的类型应该是xxxx这个类型的集合。这段代码是我们按照思路假想和推导出来的。实际上,.Net 中提供了 Type类 和 System.Reflection命名空间来帮助解决我们现在的问题。

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

     

    创建Type对象有很多种方法,本例中,我们使用typeof操作符来进行,并传递BookingStatus枚举:

    Type enumType = typeof(BookingStatus);

    然后,我们应该想办法获取 封装了字段信息的对象 的集合。Type类提供 GetFields()方法来实现这一过程,它返回一个 FieldInfo[] 数组。实际上,也就是上面我们enumFields集合的类型。

    FieldInfo[] enumFields = enumType.GetFields();

    现在,我们就可以遍历这一集合:

    foreach (FieldInfo field in enumFields)
    {
        if (!field.IsSpecialName)
        {
           DataRow row = table.NewRow();
           row[0] = field.Name;     // 获取字段文本值
           row[1] = Convert.ToInt32(myField.GetRawConstantValue()); // 获取int数值
           table.Rows.Add(row);
        }
    }

    这里field的Name属性获取了枚举的文本,GetRawConstantValue()方法获取了它的int类型的值。

    我们看一看完整的代码:

    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;
     }

    注意,SetListControl()方法依然存在并有效,只是为了节省篇幅,我没有复制过来,它的使用和之前是一样的,我们只是修改了GetDataTable()方法。

    5.使用泛型来达到代码重用

    观察上面的代码,如果我们现在有另一个枚举,叫做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);

    NOTE:如果你对泛型不熟悉,请参阅 C# 中的泛型 一文。上面的实现并没有考虑到性能的问题,仅仅为了引出反射使用的一个实例。

    6 .Net 中反射的一个范例。

    不管是VS2005的智能提示,还是修改变量名时的重构功能,都使用了反射功能。在.Net FCL中,也经常能看到反射的影子,这里就向大家演示一个最常见的例子。大家知道,在CLR中一共有两种类型,一种是值类型,一种是引用类型。声明一个引用类型的变量并对类型实例化,会在应用程序堆(Application Heap)上分配内存,创建对象实例,然后将对象实例的内存地址返回给变量,变量保存的是内存地址,实际相当于一个指针;声明一个值类型的实例变量,则会将它分配在线程堆栈(Thread Stack)上,变量本身包含了值类型的所有字段。

    现在假设我们需要比较两个对象是否相等。当我们比较两个引用类型的变量是否相等时,我们比较的是这两个变量所指向的是不是堆上的同一个实例(内存地址是否相同)。而当我们比较两个值类型变量是否相等时,怎么做呢?因为变量本身就包含了值类型所有的字段(数据),所以在比较时,就需要对两个变量的字段进行逐个的一对一的比较,看看每个字段的值是否都相等,如果任何一个字段的值不等,就返回false。

    实际上,执行这样的一个比较并不需要我们自己编写代码,Microsoft已经为我们提供了实现的方法:所有的值类型继承自 System.ValueType, ValueType和所有的类型都继承自System.Object ,Object提供了一个Equals()方法,用来判断两个对象是否相等。但是ValueType覆盖了Object的Equals()方法。当我们比较两个值类型变量是否相等时,可以调用继承自ValueType类型的Equals()方法。

    public struct ValPoint {
        public int x;
        public int y;
    }
    static void Main(string[] args) {
        bool result;

        ValPoint A1;
        A1.x = A1.y = 3;

        ValPoint B1 = A1;            // 复制A的值给B
        result = A1.Equals(B1);
        Console.WriteLine(result);      // 输出 True;
    }

    你有没有想到当调用Equals()方法时会发生什么事呢?前面我们已经提到如果是值类型,会对两个变量的字段进行逐个的比较,看看每个字段的值是否都相等,但是如何获取变量的所有字段,遍历字段,并逐一比较呢?此时,你应该意识到又到了用到反射的时候了,让我们使用reflector来查看ValueType类的Equals()方法,看看微软是如何做的吧:

    public override bool Equals(object obj) {
        if (obj == null) {
           return false;
        }
        RuntimeType type = (RuntimeType)base.GetType();
        RuntimeType type2 = (RuntimeType)obj.GetType();
        if (type2 != type) {
           return false;
        }
        object a = this;
        if (CanCompareBits(this)) {
           return FastEqualsCheck(a, obj);
        }
        // 获取所有实体字段
        FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        // 遍历字段,判断字段值是否相等
        for (int i = 0; i < fields.Length; i++) {
           object obj3 = ((RtFieldInfo)fields[i]).InternalGetValue(a, false);
           object obj4 = ((RtFieldInfo)fields[i]).InternalGetValue(obj, false);
           if (obj3 == null) {
               if (obj4 != null) {
                  return false;
               }
           } else if (!obj3.Equals(obj4)) {
               return false;
           }
        }
        return true;
    }

    注意到上面加注释的那两段代码,可以看到当对值变量进行比较时,是会使用反射来实现。反射存在着性能不佳的问题(不仅如此,还存在着很多的装箱操作),由此可见,在值类型上调用Equals()方法开销是会很大的。但是这个例子仅仅为了说明反射的用途,我想已经达到了目的。上面的代码不能完全理解也不要紧,后面会再提到。

    7.小结

    看到这里,你应该对反射有了一个初步的概念(或者叫反射的一个用途):反射是一种宽泛的叫法,它通过 System.Reflection 命名空间 并 配合 System.Type 类,提供了在运行时(Runtime)对于 类型和对象(及其成员)的基本信息 以及 元数据(metadata)的访问能力。


     

  • 相关阅读:
    vue前台(八)
    npm 淘宝镜像的配置
    详解数组中的reduce方法
    javaScript中的布尔值判断
    vue前台(七)
    vue前台(六点二)
    vue前台(六点一)
    vue前台(五)
    Android涂鸦技术及刮刮乐示例分析
    Android控件ActionBar浅析及适配2.x的方法介绍
  • 原文地址:https://www.cnblogs.com/johnwonder/p/1672244.html
Copyright © 2020-2023  润新知