.net中实现运行时从字符串动态创建对象

看到标题,大部分会说“运行时创建对象”那不是小儿科,就这样:

Dim newButton As Button = New Button()

newButton.Name = "Button1"

这的确是在运行时创建了一个按钮。不过若需按照用户要求创建按钮、复选框或者单选框怎么办,好像也好办:

Dim newControl As Control

Select Case userSelection

Case " 按钮 "

newControl = New Button()

Case " 复选框 "

newControl = New CheckBox()

....

End Select

如果用户需要的是 Windows.Forms 里面的数十种控件,那么你的 Select 语句也要写数十行吗?我当然不是想要做这种刁难的用户,但是需求总是多种多样的,若有一种方法能够在运行时任意指定对象的创建类型,甚至是用表示类型的名字的字符串创建所需的对象,该有多么方便。 .net Framwork 的反射机制给我们带来了解决问题的方法。这里,若只需要创建一般的对象,我们可以通过 System.Activator 来实现,而较复杂的我们可以通过获取构造方法来实现。

反射 Reflection 是 .net 中重要机制,很多人已经介绍过反射,我们来简单复习一下。通过反射,可以在运行时获得 .net 中每一个类型(包括类、结构、委派、接口、枚举)的成员,包括方法、属性、事件以及构造函数等,还可以获得每个成员的名称、限定符和参数等,有了反射,就可以对每一个类型了如指掌。如果获得了构造函数的信息,就可以直接创建对象,即使这个对象的类型在编译的时候还不知道。

在完成运行时创建控件这一任务前,我们先看一个简单的例子,建立一个名为 VBAppliction 的 Windows 程序,添加一个新文件,输入一个新类:

Public Class MyClassTest

Private MyField As String

Public Sub New()

MyField = "Hi!"

End Sub

Public Sub Hello()

Console.WriteLine(MyField)

End Sub

End Class

然后加给窗体入一个新按钮,输入以下事件代码:

' 方法一

Dim t As Type = GetType(MyClassTest)

o = System.Activator.CreateInstance(t)

o.Hello()

第一行 GetType(MyClassTest) 函数就已经获得了我们创建的类的类型对象( C# 中使用 typeof 函数)。接下来,我们用了 System.Activator 类的一个静态方法 CreateInstance 创建出对象实例,并将对象引用赋给 o 。 Activator 是一个用来在创建本地或远程对象的工具。运行这个程序,我们可以从 Commond Window (命令窗口,一般在调试状态 IDE 的右下方)看到 WriteLine 函数运行的结果,可以看到正确建立的对象。

如果我们用的类具有比较复杂的构造函数,还可以使用构造函数创建所需的对象,代码如下:

' 方法二

Dim t As Type = GetType(MyClassTest)

Dim c As System.Reflection.ConstructorInfo

Dim types() As Type

ReDim types(-1)

c = t.GetConstructor(Reflection.BindingFlags.Instance _

Or Reflection.BindingFlags.Public, _

Nothing, Reflection.CallingConventions.HasThis, types, Nothing)

Dim params() As Object

ReDim params(-1)

o = c.Invoke(params)

o.Hello()

这里我们创建一个 System.Reflection.ConstructorInfo 的对象,通过它可以获得类构造方法的信息。我们用的是 Type 类的 GetConstructor 方法来搜索可用的构造方法。

需要解释一下的是 types() 数组,这个数组是搜索构造方法所用的参数类型表。我们的类的构造方法没有参数,所以需要一个空但不为 Nothing ( C# 中为 null )的数组, ReDim types(-1) 就是建立这种数组的语句,在 C# 中可写作:

types = new Type[0] ;

若构造方法是这样:

Public Sub New(ByVal A As Integer, B As String)

那么相应的 types 数组就应该是

Dim types(1) As Type

types(0) = GetType(Int32)

types(1) = GetType(String)

Reflection.BindingFlags.Instance 和 Reflection.BindingFlags.Public 是一个位屏蔽,是指定搜索方式的选项。

params() 数组是构造方法的参数内容表,同样因没有参数,我们使用 ReDim -1 的语法。

Invoke 方法执行了构造方法,创建出对象实例。

现在我们回到第一种实现方法,将代码改一下,将

Dim t As Type = GetType(MyClassTest)

改为

Dim t As Type = Type.GetType("VBApplication.MyClassTest")

运行的结果没有改变,这就是说,我们实现了从字符串创建对象!不过这里 GetType 方法的使用有限制,具体我们后面再说。现在就可以实现我们的愿望:动态创建控件。通过上面的知识,我们很容易写出一个动态创建窗口控件的子程序:

Private Function CreateNewControls(ByVal targetControls As Control.ControlCollection, ByVal ctlName As String, ByVal ctlType As Type, ByVal ctlSize As Drawing.Size, ByVal ctlLocation As Drawing.Point) As Control

Dim toCreate As Control

toCreate = CType(System.Activator.CreateInstance(ctlType), Control)

toCreate.Name = ctlName

toCreate.Size = ctlSize

toCreate.Location = ctlLocation

targetControls.Add(toCreate)

Return toCreate

End Function

那一句较长的语句中包含了上一个例子中的所有内容。如果用 C# 书写,则可以写成

toCreate = (Control)System.Activator.CreateInstance(ctlType) ;

我们将按钮的事件过程改成:

Dim c As Control = Me.CreateNewControls1(Me.Controls, "Control1", GetType(CheckBox), New Size(168, 40), New Point(64, 176))

c.Text = "New Creation"

现在,单击一下按钮,就可以看到一个新的 CheckBox 出现在窗口上,标题为 New Creation ,而且,如果编写了事件过程,还可以为新建的控件添加事件响应。

看来一切都达到目的了?注意这一句 GetType(CheckBox) 还是使用了类名的字面表示,无法达到用字符串创建对象的功能。如果我们把这一句改成 Type.GetType("System.Windows.Forms.CheckBox") 行不行?嗯,试验一下,呵呵,出错了。为什么会这样? Type.GetType() 方法从字符串获得类型仅限于 corlib 中的类型或者工程内部的类型,如果是来自于外部的程序集就需要加以程序集的名称。 Windows.Forms 程序集是公有的程序集,是位于程序集缓存中的,可以在 .net Framwork 内部实现 side by side 执行。所以这个程序集有不同的版本,为了确定使用的版本,我们不仅要提供程序集的名称,还要提供程序集的版本和强名称。按照这个思路,在我使用的 .net Framework 1.1 上,将这一句写成 Type.GetType("System.Windows.Forms.CheckBox, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b 77a 5c 561934e089") 。现在运行就没有问题了。问题是我们如何取得所用 Windows.Forms 程序集的版本和强名称?可以用 GetType(CheckBox).AssemblyQualifiedName 这样的语法,一旦得到了这些信息,我们就可以将这些信息用于其它任何控件,因为他们都来自于同一个版本 Windows.Forms 程序集。现在可以来玩一个好玩的,放一个文本框到窗口上,比如叫做 TextBox1 ,将按钮的事件过程改为:

Try

Dim c As Control = Me.CreateNewControls1(Me.Controls, "Control1", Type.GetType("System.Windows.Forms." & TextBox1.Text & ", System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b 77a 5c 561934e089"), New Size(168, 40), New Point(64, 176))

c.Text = "New Creation"

Catch ex As Exception

MsgBox(ex.Message)

End Try

现在只要在 TextBox1 种输入“ Button ”,按下按钮,一个新按钮产生了!如果输入的是 CheckBox ,那么将产生一个复选框。现在无论用户怎样刁难,控件都能正确“按需创建”了。反射机制在 .net 中还有很多用途,据说 Delphi.net 中的类引用及虚拟构造函数等功能用于 .net Framwork 时就是借助于反射及 System.Type 类型实现的,善用这一利器会给你的程序增色不少。

Published At
Categories with Web编程
Tagged with
comments powered by Disqus