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 所示。
然后为程序添加命名空间: using System.Reflecton;
单击btnLoadForm控件,写入以下代码:
private void btnLoadForm_Click(object sender, System.EventArgs e){
Assembly assembly = Assembly.LoadFrom(@"e:\AutoFormFirstForm.dll");
Type type = assembly.GetType("FirstForm.Form1");
object obj = Activator.CreateInstance(type);
Form formToShow = (Form)obj;
formToShow.MdiParent = this;
formToShow.Show();
}
代码说明: 1)首先是通过Assembly.LoadFrom()来加载dll文件; 2)再通过GetType()来获得要创建的Form类对象的类型。注意,在GetType()方法的参数为类型的名字,为string类型,同时该名字应为类型的FullName,即:命名空间名.类名; 3)然后通过Activator.CreateInstance()方法创建该类型对象,返回object对象。 4)再将该对象强制转换为Form类型。 5)最后调用即可。
运行程序,单击按钮,结果如下:
结论:可以看到,对于.Net自身提供的类对象,我们对它直接强制转换即可。不会出现任何问题。
二、自定义对象
前面已经说过,对于自定义的对象,进行强制转换会抛出异常。因此,我们需要做些变通才行。
我们说,动态加载的Dll和手工添加的dll引用,系统会认为不是同一个Assembly。那么应该怎么解决?想一想,对了,应该使用接口。但是,这里使用接口的方法稍微有点特殊。还是先按步骤来讲解吧。 1、创建一个接口,该接口包括要加载对象类的方法、属性等:
新建一个“类库”项目,取名为AutoObjectInterface:
using System;namespace AutoObjectInterface
{
public interface IAutoObject
{
void Print(string s);
}
}
这个接口很简单,只是提供一个Print()方法而已。
然后将它编译为Dll文件,名为AutoObjectInterface。
2、创建自定义类对象:
新建一个“类库”项目,取名为AutoObject,添加前面创建的接口Dll引用:
using System;namespace AutoObject
{
public class TestObject:AutoObjectInterface.IAutoObject
{
public TestObject()
{
}
public void Print(string s)
{
Console.WriteLine(s);
}
}
}
这个类实现了第一步创建的接口。注意,这里实现的接口不是直接写在该类中,而是独立的Dll。在这个类中,是添加了该接口的dll,然后再实现它。这就是前面说的使用接口的一点特殊性。为什么要这样,是因为后面动态加载时,也要引用该接口Dll。我们动态创建后的对象,正是转换为该接口对象。由于实际的类和动态创建的类都引用并实现了该接口Dll,因此它的转换才能成功。这正是实现的关键!也许有人疑问:我们能否将接口就放在要创建的类中,然后实现它。编译成dll文件,然后动态加载该dll,同时也手动添加该dll。动态创建后的对象,再强制转换为这个接口类型,不可以吗?答案当然是否定的,为什么?别问我,我也不知道!总之,我现在讲的方法,才是通过反射动态创建自定义对象的不二法门!!
也许会有人说我太武断!如果你不信,去试试。如果用另外的方法能成功,我一定改正错误。至少现在我能这样武断。
言归正传。现在我们再将给类编译为dll。名为AutoObject.dll,放到e:/NewObject中。
3、利用反射动态创建该对象:
新建一个控制台项目,取名为StudyReflection,添加前面创建的接口Dll引用。代码如下:AutoObjectInterface.IAutoObject
Obj = (AutoObjectInterface.IAutoObject)obj;
using System;
using System.Reflection;
namespace studyReflection
{
class Class1
{
// 应用程序的主入口点。
[STAThread]
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom(@"e:NewObjectAutoObject.dll");
Type type = assembly.GetType("AutoObject.TestObject");
object obj = Activator.CreateInstance(type);
AutoObjectInterface.IAutoObject iObj = (AutoObjectInterface.IAutoObject)obj;
iObj.Print("wayfarer");
Console.ReadLine();
}
}
}
说明:这个代码和前面创建.Net自身提供的对象差不多,关键的区别就是强制转换。因为是自定义对象,所以我们不知道转换为什么对象啊,所以要添加接口的引用。转换的时候就转换为接口的类型:
这样我们就可以通过接口对象实例来调用类对象的方法Print()了。运行后,一切OK。
结论:在通过反射动态创建对象时,一定要注意区别所创建对象的类型。如果是自定义对象,必须通过单独的接口,来进行类型的转换。