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

    动态创建对象

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

    我们新建一个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.使用无参数构造函数创建对象

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

    1.1 使用 Assembly 的 CreateInstance 方法

    static void Main(string[] args)
    {
        Assembly asm = Assembly.GetExecutingAssembly();
        Object obj = asm.CreateInstance("Reflection4.Calculator", true);
    }

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

    1.2 调用 Activator 类的静态方法 CreateInstance:

    static void Main(string[] args)
    {
        ObjectHandle handler = Activator.CreateInstance(null, "Reflection4.Calculator");
        object obj = handler.Unwrap();
    }

    其中CreateInstance的第一个参数说明是程序集的名称,为null时表示当前程序集;第二个参数说明要创建的类型名称。Activator.CreateInstance返回的是一个ObjectHandle对象,必须进行一次Unwrap()才能返回Object类型,进而可以强制转换成我们需要的类型(本例中是Calculator)。ObjectHandle包含在System.Runtime.Remoting命名空间中,可见它是Remoting相关的,对于Remoting我暂时没有做太多研究,我们现在只要知道可以通过这种方式创建对象就可以了。

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

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

    static void Main(string[] args)
    {
        // 有参数构造函数创建对象 
        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); 
    }

    我们看一下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添加两个方法,一个实例方法,一个静态方法:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    namespace Reflection4
    {
        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;
            }
     
            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类型的实例):

    static void Main(string[] args)
    {
        //Assembly asm = Assembly.GetExecutingAssembly();
        //object obj = asm.CreateInstance("Reflection4.Calculator", true);
     
        // 有参数构造函数创建对象 
        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);
        Type t = typeof(Calculator);
        int result = (int)t.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj, null);
        Console.WriteLine("The result is {0}", result);
    }

    输出:
    image

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

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

    static void Main(string[] args)
    {
        //Assembly asm = Assembly.GetExecutingAssembly();
        //object obj = asm.CreateInstance("Reflection4.Calculator", true);
     
        // 有参数构造函数创建对象 
        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);
        
        Type t = typeof(Calculator);
        int result = (int)t.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj, null);
        Console.WriteLine("The result is {0}", result);
     
        Console.WriteLine("------------------------------");
     
        Object[] parameters2 = new Object[2];
        parameters2[0] = 6;
        parameters2[1] = 9;
        t.InvokeMember("Add", BindingFlags.InvokeMethod, null, typeof(Calculator), parameters2); 
    }

    输出:
    image

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

    2. 使用 MethodInfo.Invoke 调用方法

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

    static void Main(string[] args)
    {
        // 有参数构造函数创建对象 
        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);
     
        Type t = typeof(Calculator);
        MethodInfo mi = t.GetMethod("Add", BindingFlags.Public | BindingFlags.Instance);
        mi.Invoke(obj,null);            
    }

    输出:
    image 请按任意键继续. . .

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

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

    static void Main(string[] args)
    {
        // 有参数构造函数创建对象 
        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);
     
        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);  也可以这样 
    }

    输出:
    image

    可以看到与上面的大同小异,在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。

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Color.aspx.cs" Inherits="Reflection_Color" %>
     
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>反射遍历System.Drawing.Color结构</title>
        <style type="text/css"> 
            body{font-size:14px;}         
            h1{font-size:26px;}         
            #pnColors
            {
                750px;
            } 
            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>
        <asp:Panel ID="pnColors" runat="server">
        </asp:Panel>
    </body>
    </html>

    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);
        // 获取属性 
        PropertyInfo[] properties = t.GetProperties(BindingFlags.Static | BindingFlags.Public);
        Div div;
        // 遍历属性 
        foreach (PropertyInfo p in properties)
        {
            // 动态获得属性 
            Color 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了,如果打开页面,应该可以看到类似这样的效果:

    image

    image

    image

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

  • 相关阅读:
    [BTS2004]一步一步学习BizTalk2004 Sql Server Adapter
    [JWT]安装配置AdobeWorkFlowServer
    [BizTalk][MSMQAdapter]如何使用MSMQ的优先级设置呢?
    [JWS]Adobe WorkFlow 学习笔记(二)
    [RS]消息订阅应用实例(一)
    [BizTalk][Pipeline]使用Pipeline(一)
    ActiveDirectoryLib
    [ASP.NET]10 Tips for Writing HighPerformance Web Applications
    [UML]始
    [学习笔记][C++Primer Plus]String类的使用
  • 原文地址:https://www.cnblogs.com/SkySoot/p/2373906.html
Copyright © 2011-2022 走看看