C# 利用反射动态创建对象

时间:2021-06-17 19:25:35

C# 利用反射动态创建对象

在VS.Net中,有很多种方法动态调用对象的构造函数。一是通过Activator类的CreateInstance()方法。这个方法我们在Remoting中也用过。它实际上是在本地或从远程创建对象类型,或获取对现有远程对象的引用。它的方法签名是:public static object CreateInstance(Type);(还有其他重载方法)注意它的返回值为object,MSDN对返回值的描述是:对新创建对象的引用。

二是通过Assembly类的方法CreateInstance()。方法名和前一样,不过它不是静态方法。Assembly是在System.Reflection命名空间中。方法签名:public object CreateInstance(Type);(同样还有其他重载方法)返回值仍然是object,MSDN对返回值的描述是:表示该类型的 Object 的实例,其区域性、参数、联编程序和激活属性设置为空引用(Visual Basic 中为 Nothing),并且 BindingFlags 设置为 Public 或 Instance,或者设置为空引用 (Nothing)(如果没有找到 typeName)。

当然还有其他方法,例如通过MethodInfo获得方法信息后,根据IsConstructor属性,判断是否构造函数,再根据GetParamters()方法获得参数,最后通过Invoke()方法来调用,等等……。大家可以参考MSDN。

在这里,我且把问题简单化,只调用其默认构造函数。通过CreateInstance()方法获得object对象,再转换为实际的自定义对象类型。事实证明,这种转换为出现异常。根本的原因我还弄不清楚,初步的猜测,对于动态加载的assembly,和手动添加的assembly,Framework将两者视为了不同的assembly,即使我们使用的是同一个DLL。

我也注意到Actovator.CreateInstance()返回的是新创建对象的引用。是否是引用再作怪呢?但Assembly.CreateInstance()方法,根据MSDN的描述,返回的是object实例,然而仍然会抛出同样的异常。所以,出现问题的具体原因,我确实无法解释。

确实VS.Net博大精深,很多内在的运行机制我们不得而知。好吧,我们就知其然而不知其所以然吧,至少我现在已经知道了利用反射动态创建对象的解决之道!管它这么多,只要会用就行,退而求其次,也未尝不可。

利用反射动态创建对象,事实上就是通过Assembly动态加载DLL。这里所谓的“对象”,应分为两种类型。不同的类型,解决的办法也不相同。

一、.Net自身提供的类对象,例如Form对象、Control对象。

这也是我们在程序开发中会经常用到的。一般我们开发应用程序,都是将界面定义好。有多少个窗口,有多少个控件,事先做好,放在项目中。但有时作交互设计时,还需要考虑用户的请求。也许用户希望某些窗体能够自己决定加载的时间。也就是说,需要提供运行时加载的功能。这时,就需要通过反射来动态创建对象了。(加载的窗体对象dll,通常是放在配置文件中。在.Net中,有专门的配置文件,它是xml格式。有关配置文件,我希望能专门写一篇文章。在本文,我的例子是固定的加载程序集。) 1、创建要动态加载的窗体对象

首先,创建一个窗体对象FirstForm,这个窗体只有一个控件Lable,来显示窗体的名称。然后我们将它编译为dll文件FirstForm.dll,放在e:/AutoForm中。(要生成Dll文件,而不是exe,请在Solution Explorer(解决方案资源管理器)中的 FirstForms 项目上单击鼠标右键,选择 Properties(属性)。在 Output Type(输出类型)组合框中选择 Class Library(类库)。)

这个对象的程序集名为FirstForm.dll,类型为FirstForm.Form1。

2、创建应用程序,动态加载该对象

启动一个新的 Windows 窗体项目。将其命名为 AutoLoadForm。在新项目中包含的空窗体 Form1 中,将它的IsMdiContainer属性更改为True。这样,该窗体即变成一个 MDI 父窗体。更改窗体的大小,使窗体的长和宽的尺寸大约为默认值的两倍。

将一个面板控件拖动到窗体上,然后设置它的Dock属性,使它靠接在窗体的顶部。更改面板的大小,使它的高度大约为 50px。

将一个组合框拖动到面板上。将它命名为 cboForms,然后将它的DropDownStyle设置为DropDownList。

最后,将一个按钮拖动到面板上。将它命名为 btnLoadForm,然后将它的Text属性设置为LoadForm。

此时,Form1 应如图 1 所示。

C# 利用反射动态创建对象

然后为程序添加命名空间: using System.Reflecton;

单击btnLoadForm控件,写入以下代码:

private void btnLoadForm_Click(object sender, System.EventArgs e)
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象 Assembly assembly = Assembly.LoadFrom(@"e:\AutoFormFirstForm.dll");
C# 利用反射动态创建对象 Type type = assembly.GetType("FirstForm.Form1");
C# 利用反射动态创建对象object obj = Activator.CreateInstance(type);
C# 利用反射动态创建对象 Form formToShow = (Form)obj;
C# 利用反射动态创建对象 formToShow.MdiParent = this;
C# 利用反射动态创建对象 formToShow.Show();
C# 利用反射动态创建对象 }

代码说明: 1)首先是通过Assembly.LoadFrom()来加载dll文件; 2)再通过GetType()来获得要创建的Form类对象的类型。注意,在GetType()方法的参数为类型的名字,为string类型,同时该名字应为类型的FullName,即:命名空间名.类名; 3)然后通过Activator.CreateInstance()方法创建该类型对象,返回object对象。 4)再将该对象强制转换为Form类型。 5)最后调用即可。

运行程序,单击按钮,结果如下:

C# 利用反射动态创建对象

结论:可以看到,对于.Net自身提供的类对象,我们对它直接强制转换即可。不会出现任何问题。

二、自定义对象

前面已经说过,对于自定义的对象,进行强制转换会抛出异常。因此,我们需要做些变通才行。

我们说,动态加载的Dll和手工添加的dll引用,系统会认为不是同一个Assembly。那么应该怎么解决?想一想,对了,应该使用接口。但是,这里使用接口的方法稍微有点特殊。还是先按步骤来讲解吧。 1、创建一个接口,该接口包括要加载对象类的方法、属性等:

新建一个“类库”项目,取名为AutoObjectInterface:

using System;
C# 利用反射动态创建对象
C# 利用反射动态创建对象namespace AutoObjectInterface
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象public interface IAutoObject
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象void Print(string s);
C# 利用反射动态创建对象 }
C# 利用反射动态创建对象}

这个接口很简单,只是提供一个Print()方法而已。

然后将它编译为Dll文件,名为AutoObjectInterface。

2、创建自定义类对象:

新建一个“类库”项目,取名为AutoObject,添加前面创建的接口Dll引用:

using System;
C# 利用反射动态创建对象
C# 利用反射动态创建对象namespace AutoObject
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象public class TestObject:AutoObjectInterface.IAutoObject
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象public TestObject()
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象
C# 利用反射动态创建对象 }
C# 利用反射动态创建对象
C# 利用反射动态创建对象public void Print(string s)
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象 Console.WriteLine(s);
C# 利用反射动态创建对象 }
C# 利用反射动态创建对象 }
C# 利用反射动态创建对象}

这个类实现了第一步创建的接口。注意,这里实现的接口不是直接写在该类中,而是独立的Dll。在这个类中,是添加了该接口的dll,然后再实现它。这就是前面说的使用接口的一点特殊性。为什么要这样,是因为后面动态加载时,也要引用该接口Dll。我们动态创建后的对象,正是转换为该接口对象。由于实际的类和动态创建的类都引用并实现了该接口Dll,因此它的转换才能成功。这正是实现的关键!也许有人疑问:我们能否将接口就放在要创建的类中,然后实现它。编译成dll文件,然后动态加载该dll,同时也手动添加该dll。动态创建后的对象,再强制转换为这个接口类型,不可以吗?答案当然是否定的,为什么?别问我,我也不知道!总之,我现在讲的方法,才是通过反射动态创建自定义对象的不二法门!!

也许会有人说我太武断!如果你不信,去试试。如果用另外的方法能成功,我一定改正错误。至少现在我能这样武断。

言归正传。现在我们再将给类编译为dll。名为AutoObject.dll,放到e:/NewObject中。

3、利用反射动态创建该对象:

新建一个控制台项目,取名为StudyReflection,添加前面创建的接口Dll引用。代码如下:AutoObjectInterface.IAutoObject

Obj = (AutoObjectInterface.IAutoObject)obj;

C# 利用反射动态创建对象using System;
C# 利用反射动态创建对象using System.Reflection;
C# 利用反射动态创建对象
C# 利用反射动态创建对象
C# 利用反射动态创建对象namespace studyReflection
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象class Class1
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象
C# 利用反射动态创建对象// 应用程序的主入口点。
C# 利用反射动态创建对象[STAThread]
C# 利用反射动态创建对象static void Main(string[] args)
C# 利用反射动态创建对象C# 利用反射动态创建对象C# 利用反射动态创建对象{
C# 利用反射动态创建对象Assembly assembly = Assembly.LoadFrom(@"e:NewObjectAutoObject.dll");
C# 利用反射动态创建对象Type type = assembly.GetType("AutoObject.TestObject");
C# 利用反射动态创建对象
C# 利用反射动态创建对象object obj = Activator.CreateInstance(type);
C# 利用反射动态创建对象AutoObjectInterface.IAutoObject iObj = (AutoObjectInterface.IAutoObject)obj;
C# 利用反射动态创建对象
C# 利用反射动态创建对象iObj.Print("wayfarer");
C# 利用反射动态创建对象
C# 利用反射动态创建对象Console.ReadLine();
C# 利用反射动态创建对象}
C# 利用反射动态创建对象}
C# 利用反射动态创建对象}

说明:这个代码和前面创建.Net自身提供的对象差不多,关键的区别就是强制转换。因为是自定义对象,所以我们不知道转换为什么对象啊,所以要添加接口的引用。转换的时候就转换为接口的类型:

这样我们就可以通过接口对象实例来调用类对象的方法Print()了。运行后,一切OK。

结论:在通过反射动态创建对象时,一定要注意区别所创建对象的类型。如果是自定义对象,必须通过单独的接口,来进行类型的转换。