zoukankan      html  css  js  c++  java
  • .Net 中的反射(动态创建类型实例) Part.4

    http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx

     

    动态创建对象

    在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它。可以说,前面三节,我们学习的都是反射是什么,在接下来的章节中,我们将学习反射可以做什么。在进行更有趣的话题之前,我们先看下如何动态地创建一个对象。

    我们新建一个Console控制台项目,叫做Reflection4(因为本文是Part4,你也可以起别的名字)。然后,添加一个示范类,本文中将通过对这个示范类的操作来进行说明:

    public class Calculator {

        private int x;
        private int y;

        public Calculator(){
           x = 0;
           y = 0;
        }

        public Calculator(int x, int y) {
           this.x = x;
           this.y = y;
        }
    }

    1.使用无参数构造函数创建对象

    上面这个类非常简单,它包含两个构造函数,一个是有参数的构造函数,一个是无参数的构造函数,我们先看看通过反射,使用无参数的构造函数创建对象。创建对象通常有两种方式,一种是使用Assembly的CreateInstance方法:

    Assembly asm = Assembly.GetExecutingAssembly();          
    Object obj = asm.CreateInstance("Reflection4.Calculator", true);
    // 输出:Calculator() invoked

    CreateInstance的第一个参数代表了要创建的类型实例的字符串名称,第二个参数说明是不是大小写无关(Ignore Case)。注意到CreateInstance返回的是一个Object对象,意味着如果想使用这个对象,需要进行一次类型转换。

    创建对象的另一种方式是调用Activator类的静态方法CreateInstance:

    ObjectHandle handler = Activator.CreateInstance(null, "Reflection4.Calculator");
    Object obj = handler.Unwrap();

    其中CreateInstance的第一个参数说明是程序集的名称,为null时表示当前程序集;第二个参数说明要创建的类型名称。Activator.CreateInstance返回的是一个ObjectHandle对象,必须进行一次Unwrap()才能返回Object类型,进而可以强制转换成我们需要的类型(本例中是Calculator)。ObjectHandle包含在System.Runtime.Remoting命名空间中,可见它是Remoting相关的,实际上ObjectHandle类只是一个对原类型进行了一个包装以便进行封送,更多内容可以参见 .Net Remoting(应用程序域)—Part.1 这篇文章。

    2.使用有参数构造函数创建对象

    如果我们想通过有参数的构造函数创建对象,我们可以使用Assembly的CreateInstanc()的重载方法:

    // 有参数构造函数创建对象
    Assembly asm = Assembly.GetExecutingAssembly();
    Object[] parameters = new Object[2];    // 定义构造函数需要的参数
    parameters[0] = 3;
    parameters[1] = 5;

    Object obj = asm.CreateInstance("Reflection4.Calculator", true, BindingFlags.Default, null, parameters, null, null);

    // 输出:Calculator(int x, int y) invoked

    我们看一下CreateInstance需要提供的参数:

    1. 前两个在前一小节已经说明过了;
    2. BindingFlags在前面我们也用到过,它用于限定对类型成员的搜索。在这里指定Default,意思是不使用BingdingFlags的策略(你可以把它理解成null,但是BindingFlags是值类型,所以不可能为null,必须有一个默认值,而这个Default就是它的默认值);
    3. 接下来的参数是Binder,它封装了CreateInstance绑定对象(Calculator)的规则,我们几乎永远都会传递null进去,实际上使用的是预定义的DefaultBinder;
    4. 接下来是一个Object[]数组类型,它包含我们传递进去的参数,有参数的构造函数将会使用这些参数;
    5. 接下来的参数是一个CultureInfo类型,它包含了关于语言和文化的信息(简单点理解就是什么时候ToString("c")应该显示“¥”,什么时候应该显示“$”)。

    动态调用方法

    接下来我们看一下如何动态地调用方法。注意,本文讨论的调用不是将上面动态创建好的对象由Object类型转换成Calculator类型再进行方法调用,这和“常规调用”就没有区别了,让我们以.Net Reflection 的方式来进行方法的调用。继续进行之前,我们为Calculator添加两个方法,一个实例方法,一个静态方法:

    public int Add(){
        int total= 0;
        total = x + y;
        Console.WriteLine("Invoke Instance Method: ");
        Console.WriteLine(String.Format("[Add]: {0} plus {1} equals to {2}", x, y, total));
        return total;
    }

    public static void Add(int x, int y){
        int total = x + y;
        Console.WriteLine("Invoke Static Method: ");
        Console.WriteLine(String.Format("[Add]: {0} plus {1} equals to {2}", x, y, total));
    }

    调用方法的方式一般有两种:

    1. 在类型的Type对象上调用InvokeMember()方法,传递想要在其上调用方法的对象(也就是刚才动态创建的Calculator类型实例),并指定BindingFlags为InvokeMethod。根据方法签名,可能还需要传递参数。
    2. 先通过Type对象的GetMethond()方法,获取想要调用的方法对象,也就是MethodInfo对象,然后在该对象上调用Invoke方法。根据方法签名,可能还需要传递参数。

    需要说明的是,使用InvokeMember不限于调用对象的方法,也可以用于获取对象的字段、属性,方式都是类似的,本文只说明最常见的调用方法。

    1.使用InvokeMember调用方法

    我们先看第一种方法,代码很简单,只需要两行(注意obj在上节已经创建,是Calculator类型的实例):

    Type t = typeof(Calculator);
    int result = (int)t.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj, null);
    Console.WriteLine(String.Format("The result is {0}", result));

    输出:
    Invoke Instance Method:
    [Add]: 3 plus 5 equals to 8
    The result is 8

    在InvokeMember方法中,第一个参数说明了想要调用的方法名称;第二个参数说明是调用方法(因为InvokeMember的功能非常强大,不光是可以调用方法,还可以获取/设置 属性、字段等。此枚举的详情可参看Part.2或者MSDN);第三个参数是Binder,null说明使用默认的Binder;第四个参数说明是在这个对象上(obj是Calculator类型的实例)进行调用;最后一个参数是数组类型,表示的是方法所接受的参数。

    我们在看一下对于静态方法应该如何调用:

    Object[] parameters2 = new Object[2];
    parameters2[0] = 6;
    parameters2[1] = 9;
    t.InvokeMember("Add", BindingFlags.InvokeMethod, null, typeof(Calculator), parameters2);

    输出:
    Invoke Static Method:
    [Add]: 6 plus 9 equals to 15

    我们和上面对比一下:首先,第四个参数传递的是 typeof(Calculator),不再是一个Calculator实例类型,这很容易理解,因为我们调用的是一个静态方法,它不是基于某个具体的类型实例的,而是基于类型本身;其次,因为我们的静态方法需要提供两个参数,所以我们以数组的形式将这两个参数进行了传递。

    2.使用MethodInfo.Invoke调用方法

    我们再看下第二种方式,先获得一个MethodInfo实例,然后调用这个实例的Invoke方法,我们看下具体如何做:

    Type t = typeof(Calculator);
    MethodInfo mi = t.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public);
    mi.Invoke(obj, null);

    输出:
    Invoke Instance Method:
    [Add]: 3 plus 5 equals to 8
    请按任意键继续. . .

    在代码的第二行,我们先使用GetMethod方法获取了一个方法对象MethodInfo,指定BindingFlags为Instance和Public,因为有两个方法都命名为“Add”,所以在这里指定搜索条件是必须的。接着我们使用Invoke()调用了Add方法,第一个参数obj是前面创建的Calculator类型实例,表明在该实例上创建方法;第二个参数为null,说明方法不需要提供参数。

    我们再看下如何使用这种方式调用静态方法:

    Type t = typeof(Calculator);
    Object[] parameters2 = new Object[2];
    parameters2[0] = 6;
    parameters2[1] = 9;
    MethodInfo mi = t.GetMethod("Add", BindingFlags.Static | BindingFlags.Public);
    mi.Invoke(null, parameters2);
    // mi.Invoke(t, parameters2);   也可以这样

    输出:
    Invoke Static Method:
    [Add]: 6 plus 9 equals to 15

    可以看到与上面的大同小异,在GetMethod()方法中,我们指定为搜索BindingFlags.Static,而不是BindingFlags.Public,因为我们要调用的是静态的Add方法。在Invoke()方法中,需要注意的是第一个参数,不能在传递Calculator类型实例,而应该传递Calculator的Type类型或者直接传递null。因为静态方法不是属于某个实例的。

    NOTE:通过上面的例子可以看出:使用反射可以达到最大程度上的多态,举个例子,你可以在页面上放置一个DropDownList控件,然后指定它的Items的value为你某个类的方法的名称,然后在SelectedIndexChanged事件中,利用value的值来调用类的方法。而在以前,你只能写一些if else 语句,先判断DropDownList返回的值,根据值再决定调用哪个方法。使用这种方式,编译器在代码运行之前(或者说用户选择了某个选项之前)完全不知道哪个方法将被调用,这也就是常说的 迟绑定(Late Binding)

    Coding4Fun:遍历System.Drawing.Color结构

    我们已经讲述了太多的基本方法和理论,现在让我们来做一点有趣的事情:大家知道在Asp.Net中控件的颜色设置,比如说ForeColor, BackColor等,都是一个System.Draw.Color结构类型。在某些情况下我们需要使用自定义的颜色,那么我们会使用类似这样的方式Color.FromRgb(125,25,13)创建一个颜色值。但有时候我们会觉得比较麻烦,因为这个数字太不直观了,我们甚至需要把这个值贴到PhotoShop中看看是什么样的。

    这时候,我们可能会想要使用Color结构提供的默认颜色,也就是它的141个静态属性,但是这些值依然是以名称,比如DarkGreen的形式给出的,还是不够直观,如果能把它们以色块的形式输出到页面就好了,这样我们查看起来会方便的多,以后使用也会比较便利。我已经实现了它,可以点击下面的链接查看:

    效果预览:http://www.tracefact.net/demo/reflection/color.aspx

    基本实现

    现在我们来看一下实现过程:

    先创建页面Color.aspx(或其他名字),然后在Head里添加些样式控制页面显示,再拖放一个Panel控件进去。样式表需要注意的是#pnColors div部分,它定义了页面上将显示的色块的样式;Id为pnHolder的Panel控件用于装载我们动态生成的div。

    <head>
    <style type="text/css">
    body{font-size:14px;}
    h1{font-size:26px;}
    #pnColors div{
        float:left;140px;
        padding:7px 0;
        text-align:center;
        margin:3px;
        border:1px solid #aaa;
        font-size:11px;
        font-family:verdana, arial
    }
    </style>
    </head>

    <body>
        <h1>Coding4Fun:使用反射遍历System.Drawing.Color结构</h1>
        <form id="form1" runat="server">
           <asp:Panel ID="pnColors" runat="server"></asp:Panel>
        </form>
    </body>

    NOTE:如果将页面命名为了 Color.aspx,那么需要在代码后置文件中修改类名,比如改成:Reflection_Color,同时页面顶部也需要修改成Inherits="Reflection_Color",不然会出现命名冲突的问题。

    下一步的思路是这样的:我们在phColors中添加一系列的div,这些div也就是页面上我们将要显示的色块。我们设置div的文本为 颜色的名称 和 RGB数值,它的背景色我们设为相应的颜色(色块的其他样式,比如宽、边框、宽度已经在head中定义)。我们知道在Asp.Net中,并没有一个Div控件,只有HtmlGenericControl,此时,我们最好定义一个Div让它继承自HtmlGenericControl。

    public class Div:HtmlGenericControl
    {
        private string name;
       
        public Div(Color c)
           : base("div")     // 调用基类构造函数,创建一个 Div
        {
           this.name = c.Name;      // 颜色名称
          
           // 设置文本
           this.InnerHtml = String.Format("{0}<br />RGB({1},{2},{3})", name, c.R, c.G, c.B);

           int total = c.R + c.G + c.B;
           if (total <= 255)     // 如果底色太暗,前景色改为明色调
               this.Style.Add("color", "#eee");

           // 设置背景颜色
           this.Style.Add("background", String.Format("rgb({0},{1},{2})", c.R, c.G, c.B));
        }
    }

    如同我们前面所描述的,这个Div接受一个Color类型作为构造函数的参数,然后在构造函数中,先设置了它的文本为 颜色名称 和 颜色的各个数值(通过Color结构的R, G, B属性获得)。然后设置了div的背景色为相应的RGB颜色。

    NOTE:在上面 if(total<=255)那里,可能有的颜色本身就很暗,如果这种情况再使用黑色的前景色那么文字会看不清楚,所以我添加了判断,如果背景太暗,就将前景色调的明亮一点。

    OK,现在我们到后置代码中只要做一点点的工作就可以了:

    protected void Page_Load(object sender, EventArgs e)
    {
        List<Div> list = new List<Div>();

        Type t = typeof(Color);      // 页首已经包含了 using System.Drawing;
        // 获取属性
        PropertyInfo[] properties = t.GetProperties(BindingFlags.Static | BindingFlags.Public);
        Div div;

        // 遍历属性
        foreach (PropertyInfo p in properties)
        {
           // 动态获得属性
           Color c;         
           c = (Color)t.InvokeMember(p.Name, BindingFlags.GetProperty, null, typeof(Color), null);       
          
           div = new Div(c);
           list.Add(div);
        }

        foreach (Div item in list)   {
           pnColors.Controls.Add(item);
        }
    }

    上面的代码是很直白的:先创建一个Div列表,用于保存即将创建的色块。然后获取Color类型的Type实例。接着我们使用GetProperties()方法,并指定BindingFlags获取所有的静态公共属性。然后遍历属性,并使用InvokeMember()方法获取了属性值,因为返回的是一个Object类型,所以我们需要把它强制转换成一个Color类型。注意在这里InvokeMember的BindingFlags指定为GetProperty,意为获取属性值。第四个参数为typeof(Color),因为颜色属性(比如DarkGreen)是静态的,不是针对于某个实例的,如果是实例,则需要传递调用此属性的类型实例。最后,我们根据颜色创建div,并将它加入列表,遍历列表并逐一加入到Id为pnColors的Panal控件中。

    现在已经OK了,如果打开页面,应该可以看到类似这样的效果:

    为列表排序

    上面的页面看上去会比较乱,因为列表大致是按颜色名称排序的(Transparnet例外),我们最好可以让列表基于颜色进行排序。关于列表排序,我在 基于业务对象的排序 一文中已经非常详细地进行了讨论,所以这里我仅给出实现过程,而不再进行讲述。这一小节与反射无关,如果你对排序已经非常熟悉,可以跳过。

    在页面上添加一个RadioButtonList控件,将AutoPostBack设为true,我们要求可以按名称和颜色值两种方式进行排序:

    排序:
    <asp:RadioButtonList ID="rblSort" runat="server" AutoPostBack="true" RepeatDirection="Horizontal" RepeatLayout="Flow">
        <asp:ListItem Selected="True">Name</asp:ListItem>
         <asp:ListItem>Color</asp:ListItem>       
    </asp:RadioButtonList>

    在后置代码中,添加一个枚举作为排序的依据:

    public enum SortBy{
        Name,         // 按名称排序
        Color         // 暗颜色值排序
    }

    修改Div类,添加 ColorValue字段,这个字段代表颜色的值,并创建嵌套类型ColorComparer,以及方法GetComparer:

    public class Div:HtmlGenericControl
    {
        private int colorValue;
        private string name;
       
        public Div(Color c)
           : base("div")     // 调用基类构造函数,创建一个 Div
        {
           this.name = c.Name;      // 颜色名称

           this.colorValue =        // 颜色的色彩值
               c.R * 256 * 256 + c.G * 256 + c.B;
          
           // 设置文本
           this.InnerHtml = String.Format("{0}<br />RGB({1},{2},{3})", name, c.R, c.G, c.B);

           int total = c.R + c.G + c.B;
           if (total <= 255)     // 如果底色太暗,前景色改为明色调
               this.Style.Add("color", "#eee");

           // 设置背景颜色
           this.Style.Add("background", String.Format("rgb({0},{1},{2})", c.R, c.G, c.B));
        }
       
        // 返回一个Comparer()用于排序
        public static ColorComparer GetComparer(SortBy sort) {
           return new ColorComparer(sort);
        }

        // 默认以名称排序
        public static ColorComparer GetComparer() {
           return GetComparer(SortBy.Name);
        }
       
        // 嵌套类型,用于排序
        public class ColorComparer : IComparer<Div>
        {
           private SortBy sort;

           public ColorComparer(SortBy sort) {
               this.sort = sort;
           }

           // 实现IComparer<T>接口,根据sort判断以何为依据一进排序
           public int Compare(Div x, Div y)
           {
               if (sort == SortBy.Name)
                  return String.Compare(x.name, y.name);
               else
                  return x.colorValue.CompareTo(y.colorValue);
           }
        }
    }

    在Page_Load事件上面,我们添加语句,获取当前的排序依据(枚举):

    SortBy sort;

    if (!IsPostBack) {
        sort = SortBy.Name;
    } else {
        sort = (SortBy)Enum.Parse(typeof(SortBy), rblSort.SelectedValue);
    }

    在将列表输出到页面之前,我们调用列表的Sort方法:

    list.Sort(Div.GetComparer(sort));   // 对列表进行排序

    foreach (Div item in list)   {
        pnColors.Controls.Add(item);
    }

    好了,所有工作都完成了,再次打开页面,可以看到类似如下画面,我们可以按照名称或者颜色值来对列表进行排序显示:

    总结

    本文分三个部分讲述了.Net中反射的一个应用:动态创建对象和调用对象方法(属性、字段)。我们先学习最常见的动态创建对象的两种方式,随后分别讨论了使用Type.InvokeMember()和MethodInfo.Invoke()方法来调用类型的实例方法和静态方法。最后,我们使用反射遍历了System.Drawing.Color结构,并输出了颜色值。

    感谢阅读,希望这篇文章能给你带来帮助!

  • 相关阅读:
    Java实现 蓝桥杯VIP 基础练习 完美的代价
    Java实现 蓝桥杯VIP基础练习 矩形面积交
    Java实现 蓝桥杯VIP 基础练习 完美的代价
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法
    核心思想:想清楚自己创业的目的(如果你没有自信提供一种更好的产品或服务,那就别做了,比如IM 电商 搜索)
    在Linux中如何利用backtrace信息解决问题
  • 原文地址:https://www.cnblogs.com/zuiyirenjian/p/2607390.html
Copyright © 2011-2022 走看看