Reflection意思为反射,在.NET中,它是获取运行时类型信息的方式。一般来说,.NET的应用程序由三个部分组成,分别是程序集(Assembly)、模块(Module)、类型(class),而反射提供一种编程的方式,让程序员可以在程序运行期获得这三个组成部分的相关信息。
一、有关知识
1. System.Assembly类
Assembly允许用户访问给定程序集的元数据,它也包含可以加载和执行程序集(程序集可执行的情况下)的方法。这个功能相当强大,可以轻易地取得组件内部所有信息,并且将其应用于映射的机制。常用的有LoadFrom()方法、GetType()方法等。Assembly类可以获得正在运行的装配件信息,也可以动态地加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
2. System.Type类
Type实际上是一个抽象的基类,它有与每种数据类型对应的派生类,但一般情况下派生类只提供各种Type方法和属性的不同重载,返回对应数据类型的正确数据。它的大多数方法都是用于获取对应数据类型的成员信息,比如:构造函数、属性、方法和事件等,并且能够进行调用。常用的有GetMethod()方法、GetField()方法、GetProperty()方法等。
3. 动态调用方法
方法动态调用是指在执行期利用映射取得的类型方法成员进行动态调用,通过调用实例的Invoke()方法来实现。Invoke方法用以进行方法的动态调用,其一般的定义形式如下:
Invoke(object obj, object[] parameters)
第一个为object类型参数,这个参数对象本身所参考的对象,为所要调用的方法其本身所属的来源对象,而第二个数组对象parameters则是调用指定方法所需的参数集合,若是调用的方法不需传入参数,这个对象数组会参考至一个null值。
二、映射实例
下面通过一个实例,运用映射方法实现对Windows窗体的操纵。
1. 开发环境
操作系统:Windows XP Professional,版本5.1.2600 Service Pack 2
开发工具:Microsoft Visual Studio 2005,版本8.0.50727.42
Microsoft .NET Framework,版本2.0.50727
2. 创建被操纵的Windows窗体
首先创建一个Windows窗体应用程序,名称为DataSquare,界面如图1所示。
图1 Form1窗体的界面
由图1可以看出,Form1窗体包含五个控件:两个textBox、两个Label和一个Button,它实现的功能是计算用户输入数的平方,并将结果显示出来。Form1是一个简单的Windows窗体程序,仅实现了计算平方值的功能,代码有待完善,在这里只是以它作为被操纵的对象来介绍如何使用.NET提供的映射方法。
3. 创建控制台程序
被操纵的Form1窗体创建完后,接下来就该创建主控程序了。为了实时显示操纵过程的需要,故选择创建一个控制台应用程序,名称为ReflectionControlForm。该控制台程序需要实现的功能是:运行要操纵的Form1窗体,并且两个程序之间能够进行通信;访问和设置窗体的属性;访问和设置窗体控件的属性;调用窗体的方法,模拟用户的操作。
(1)启动Form1窗体线程
在ReflectionControlForm程序中,可以运用Process.Start()方法很方便地启动Form1窗体程序,但是要实现它们两者之间的通信,这样却行不通。因为进程之间不能够进行通信,只有同一个进程内的线程之间才能进行通信。因此,必须将Form1窗体作为线程来启动。在这里,通过创建CreatThread类,将调用窗体对象的Application.Run()方法传给ThreadStart对象,ThreadStart对象再作为Thread的构造函数,然后调用Thread.Start()方法就可以启动窗体程序了。
……
//通过创建外覆类的方法,启动一个单独的线程
CreatThread creatThread = new CreatThread(form);
ThreadStart threadStart = new ThreadStart(creatThread.ApplicationRun);
//设置线程的相关必要属性
Thread thread = new Thread(threadStart);
thread.IsBackground = true;
//启动线程
thread.Start();
……
//创建启动一个单独线程的外覆类
private class CreatThread
{
public readonly Form RunForm;
public CreatThread(Form form)
{
this.RunForm = form;
}
//通过调用Application.Run(),实例化CreatThread对象
public void ApplicationRun()
{
Application.Run(RunForm);
}
}
Form1窗体线程被启动后,便可以对它进行操纵了。
(2)设置和访问窗体属性
利用Type的GetProperty()方法得到属性后,使用PropertyInfo来了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,并获取或设置属性值,需要用到SetValue()方法和GetValue()方法。
//设置窗体属性的方法,传递的参数为窗体名称、属性名称、新设置的值
delegate void delSetPropertyForm(Form form, string property, object value);
static void SetPropertyForm(Form form, string property, object value)
{
//定义Type对象
Type type = form.GetType();
//通过GetProperty访问属性
PropertyInfo propertyinfo=type.GetProperty(property);
//通过SetValue设置属性
propertyinfo.SetValue(form, value, null);
}
//获取窗体属性的方法,传递的参数为窗体名称、属性名称
delegate object delGetPropertyForm(Form form, string property);
static object GetPropertyForm(Form form, string property)
{
//定义Type对象
Type type = form.GetType();
//通过GetProperty访问属性
PropertyInfo propertyinfo = type.GetProperty(property);
return propertyinfo.GetValue(form, null);
}
(3)设置和访问窗体控件属性
利用Type的GetField()方法找到字段后,使用FieldInfo来了解字段的名称、访问修饰符和实现详细信息,来获取字段值,需要用到GetValue()方法。然后需要再声明一个Type对象,用于PropertyInfo的SetValue()方法和GetValue()方法,来获取或设置控件的属性。需要指出的是GetField()方法需要BindingFlags参数,它定义的是返回的成员类型。
//定义应返回的成员类型
static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance;
//设置控件属性的方法,传递的参数为窗体名称、控件名称、控件属性、新设置的值
delegate void delSetPropertyControl(Form form, string control,string property, object value);
static void SetPropertyControl(Form form, string control,string property, object value)
{
//InvokeRequired初始值为true
if (form.InvokeRequired)
{
//利用递归方式
Delegate del = new delSetPropertyControl(SetPropertyControl);
object[] obj = new object[] { form, control, property, value };
//Invoke执行后,InvokeRequired值为false
form.Invoke(del, obj);
}
else
{
//定义Type对象
Type type1 = form.GetType();
//获取控件
FieldInfo fieldinfo = type1.GetField(control, flags);
object ctrl = fieldinfo.GetValue(form);
Type type2 = ctrl.GetType();
//获取控件的属性
PropertyInfo propertyinfo = type2.GetProperty(property);
propertyinfo.SetValue(ctrl, value, null);
}
}
//获取控件属性的方法,传递的参数为窗体名称、控件名称、控件属性
delegate object delGetPropertyControl(Form form, string control, string property);
static object GetPropertyControl(Form form, string control, string property)
{
//InvokeRequired初始值为true
if (form.InvokeRequired)
{
//利用递归方式
Delegate del = new delGetPropertyControl(GetPropertyControl);
object[] obj = new object[] { form, control, property };
//Invoke执行后,InvokeRequired值为false
return form.Invoke(del, obj);
}
else
{
Type type1 = form.GetType();
//获取控件
FieldInfo fieldinfo = type1.GetField(control, flags);
object ctrl = fieldinfo.GetValue(form);
Type type2 = ctrl.GetType();
//获取控件的属性
PropertyInfo propertyinfo = type2.GetProperty(property);
return propertyinfo.GetValue(ctrl, null);
}
}
通过与设置和访问窗体属性的比较,可以发现根据InvokeRequired值的变化,使用递归的方式可以将Delegate del和object[] obj两个变量的声明放在子函数里,这样能够增加程序的可读性。
(4)调用窗体方法
窗体的方法即窗体所包含的方法成员。使用MethodInfo来了解方法的名称、返回类型、参数、访问修饰符和实现详细信息等。通过Type的GetMethod()方法获取后,使用MethodInfo的Invoke()来调用方法。GetMethod()方法也需要BindingFlags参数。
//访问窗体的方法,传递的参数为窗体名称、方法名称、参数
delegate void delMethod(Form form, string method, params object[] parms);
static void Method(Form form, string method, params object[] parms)
{
//InvokeRequired初始值为true
if (form.InvokeRequired)
{
//利用递归方式
Delegate del = new delMethod(Method);
object []obj = new object[] { form, method, parms };
//Invoke执行后,InvokeRequired值为false
form.Invoke(del, obj);
}
else
{
Type type = form.GetType();
//获取方法
MethodInfo methodinfo = type.GetMethod(method, flags);
//调用方法
methodinfo.Invoke(form, parms);
}
}
(5)Main()函数
各个子函数完成以后,接下来便是编写Main()函数进行子函数的调用,其代码如下:
static void Main(string[] args)
{
Console.WriteLine("开始启动窗体程序……");
Form form = null;
//窗体caption的全称,不能简略
string formName = "DataSquare.Form1";
//可执行文件的存放路径
string path = @"DataSquare.exe";
//创建 Assembly对象,取得DataSquare类的组件
Assembly assembly = Assembly.LoadFrom(path);
//创建Type对象,将窗体的全名传给Assembly对象
Type type = assembly.GetType(formName);
//利用CreateInstance()方法得到对DataSquare.Form1的引用
form = (Form)assembly.CreateInstance(type.FullName);
//通过创建外覆类的方法,启动一个单独的线程
CreatThread creatThread = new CreatThread(form);
ThreadStart threadStart = new ThreadStart(creatThread.ApplicationRun);
//设置线程的相关必要属性
Thread thread = new Thread(threadStart);
thread.IsBackground = true;
//启动线程
thread.Start();
Console.WriteLine("窗体程序已经被启动");
//分别定义object与Delegate对象,利用委托机制实现对属性的控制
object[] obj;
Delegate del;
//延时设置,方便查看对窗体的操作
Thread.Sleep(1000);
//设置窗体的属性,以Text属性为例
Console.WriteLine("设置窗体的Text属性");
//新设置的属性值
string text = "新设置的窗体";
obj = new object[] { form,"Text",text};
//调用设置窗体属性的方法
del= new delSetPropertyForm(SetPropertyForm);
form.Invoke(del, obj);
Console.WriteLine("窗体的Text属性设置完毕");
Thread.Sleep(1000);
//获取窗体的属性,以Size属性为例
Console.WriteLine("获得窗体的Size属性");
//调用获取窗体属性的方法
del = new delGetPropertyForm(GetPropertyForm);
obj = new object[] { form,"Size"};
//获得属性值
Size size = (Size)form.Invoke(del, obj);
Console.WriteLine("窗体Size属性值是:高度为{0},宽度为{1}",size.Height,size.Width);
Thread.Sleep(1000);
//设置控件属性
Console.WriteLine("设置控件textBoxInput的Text属性为20");
//调用设置控件属性函数
SetPropertyControl(form, "textBoxInput", "Text", "20");
Console.WriteLine("控件textBoxInput的Text属性设置完毕");
Thread.Sleep(1000);
//访问窗体的方法
Console.WriteLine("调用buttonCalculate_Click方法,求平方值");
object[] parms = new object[] { null, EventArgs.Empty };
//调用访问窗体方法的函数
Method(form, "buttonCalculate_Click", parms);
Console.WriteLine("求平方运算完毕");
Thread.Sleep(1000);
//获得控件属性
Console.WriteLine("获取控件textBoxResult的Text属性");
//调用获取控件属性的函数
text = (string)GetPropertyControl(form, "textBoxResult", "Text");
Console.WriteLine("textBoxResult的Text属性为{0},即textBoxInput的Text属性的平方", text);
Thread.Sleep(1000);
}
运行结果的过程截图如图2所示。
图2 运行结果的过程截图
三、结语
通过实例,可以得到运用映射方法操纵窗体的类调用关系,如图3所示:
图3 类调用关系
使用映射方法对窗体进行操纵,该技术实现起来简单,省时并能获得较好的效果,尤其在对应用程序的用户界面进行测试时,该方法可以作为自动化测试的小工具,快捷地完成测试任务。但是该方法也有不足之处,它只能应用于.NET的应用程序,难以处理复杂的操作。
|