2009-2-17 下午
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。而其中最直接的应用包括两种:一是获取程序集的结构;一是使用程序集成员(个人认为)。下面,我以两个例子来演示这两个功能的实现。
一、获取程序集的结构
程序集是一个树形结构,下面这个例子叫“程序集查看器”,这是一个窗体程序的例子。
本程序功能:用于将任何的程序集文件(*.dll、*.pdf或*.exe)的结构的查看。
实现原理:
首先根据选择的程序集全名(包括目录和文件名)定义一个Assembly对象的实例,然后调用此对象的GetTypes()方法获取此对象下的一级子类型,接着对各个子类型的各字段、属性、方法和事件进行遍历、显示。
在此说明一下“BindingFlags”。 “Bindingflags”是一个枚举类型,用来指定控制绑定和由反射执行的成员和类型搜索方法的标志。比如其中的“BindingFlags.Static”在这里的作用是遍历范围,包含这定义为“Static”的成员。
另外,为了非常直观地显示程序集的结构,我选择了TreeView控件。
当然,此示例程序还有许多需求完善和改进的地方,比如,对程序集的显示时,没有把访问域显示出来,所以,我们还不能从显示的结果中看出此成员是“public”的,还是“private”的。另外,我们还可以加入对应成员的IL代码块显示和导出功能。
好,现在,我就把整个步骤和代码列出如下。
1. 打开Visual Studio2008,依次选择“新建”->项目,在打开的对话框中,展开Visual C#,选择Windows,在右侧选择“Windows窗体应用程序”,项目名称设置为“Reflection”。单击确定,建立一个新的窗体项目。
2. 将自动建立的窗体“Form1”的Text属性设置为“程序集查看器”,然后将窗体设置为适当大小。
3. 从工具箱中,向窗体中拖入一个SplitContainer控件,将其Dock属性设置为:Fill,将Orientation属性设置为:Horizontal。
4. 向SplitContainer的上面的Panel中拖入一个按钮,其Text属性设置为:“选择程序集文件(&O)”。此按钮的作用是,打开一个选择文件对话框,用于选择需要查看的程序集文件。
5. 在按钮后,拖入一个Label控件,将其Text值清空。
6. 从属性上的下拉列表框中选择SplitContainer控件(如下图所示),并在窗口中拖动它的Panel间隔线,使两个Panel的大小适当。
7. 向SplitContainer的下面的Panel中加入一个TreeView控件,并将其Dock属性设置为:Fill。
8. 展开工具箱中的“对话框”控件组,双击OpenFileDialog控件,添加一个OpenFileDialog控件。在属性面板中,将其Filter属性设置为:“所有文件|*.*|dll文件|*.dll|exe文件|*.exe”。
至此,窗口的界面设计完成。其界面如下图所示。
9. 现在给程序添加代码。双击按钮,在其事件中,添加如下代码:
DotNet 编写的 Dll 或 exe 文件。
{
treeView1.Nodes.Clear();
Assembly a = Assembly.LoadFile(openFileDialog1.FileName);
label1.Text = " 程序集全局信息: " + a.FullName;
Type[] arr = a.GetTypes();
BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (Type t in arr)
{
TreeNode node = new TreeNode();
node.Text = t.FullName;
FieldInfo[] arrF = t.GetFields(flags);
foreach (FieldInfo f in arrF)
{
node.Nodes.Add( " 字段: " + f.FieldType + " " + f.Name);
}
PropertyInfo[] arrP = t.GetProperties(flags);
foreach (PropertyInfo p in arrP)
{
node.Nodes.Add( " 属性: " + p.PropertyType + " " + p.Name);
}
MethodInfo[] arrM = t.GetMethods(flags);
foreach (MethodInfo m in arrM)
{
string mString = string .Format( " 方法:{0} {1}(|||) " , m.ReturnType, m.Name),
pString = "" ;
ParameterInfo[] pi = m.GetParameters();
foreach (ParameterInfo p in pi)
{
pString += p.ParameterType + " " + p.Name + " " ;
}
mString = mString.Replace( " ||| " , pString);
node.Nodes.Add(mString);
}
EventInfo[] arrE = t.GetEvents(flags);
foreach (EventInfo ei in arrE)
{
string eString = string .Format( " 事件:{0} " , ei.Name);
node.Nodes.Add(eString);
}
treeView1.Nodes.Add(node);
}
}
至此,本示例程序完成。可以把本程序作为一个小工具来使用,用它来查一下某个程序集的结构,非常简单,便于我们使用他人使用
按下F5运行此程序,单击按钮,打开“选择文件对话框”,选择一个程序集文件,如选择本程序生成的Reflection.exe,得到结果,如下图所示:
二、使用程序集成员
程序集成员包括很多种类型,如字段、方法、事件。这里,我仅以调用程序集中的方法为例来演示使用程序集成员的一般方法。
说到调用方法,有人问:我们不使用反射也可以调用程序集元数据中的方法呀!我可以对相关的dll或exe文件引用进项目,然后不就可以调用其中的方法了吗?
是的,一般情况下,使用“引用”来使用其中的方法是可以的,不过使用反射,有一定优势的。比如说,一般情况下,我们只通过“引用”的方法,是无法使用程序集中的私有方法的,而只能使用已经公开的方法或其他成员(据我目前的使用经验是这样的,高手们有不同见解,敬请指教)。可以使用反射,就可以使用程序集中所有你需要的成员。
本例原理:首先是根据用户输入的程序集路径建立一个Assemply对象,然后,获取方法所在的类的类型。而后创建两个MethodInfo对象,用于存放获取的两个方法。
另外,为了调用程序集中的私有方法,本例同样使用了BindingFlags枚举。
再次说明一下,为了测试方便和易于理解,本例所调用的程序集就是本例生成的exe文件,所调用的程序集路径和调用的方法,我已经写到代码中了。如果你想把此例变得通用,可以让程序接受输入的路径和调用的方法的名称。
代码的其他说明,我已经写到注释中了,大家有什么别的问题,可以在此提问。
我测试的运行结果如下图所示:
现给出本例代码如下:
using System.Reflection;
namespace InvokeMethod
{
/// <summary>
/// 本例通过反射实现了对数据库集文件(.dll或.exe形式)中的方法的调用
/// </summary>
class Program
{
static void Main( string [] args)
{
Assembly a = Assembly.LoadFile( @" E:\developingApp\Reflection\InvokeMethod\bin\Debug\InvokeMethod.exe " );
// Console.WriteLine(a.FullName);
BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
Type t = a.GetType( " InvokeMethod.Program " );
MethodInfo mi = t.GetMethod( " GetString " , flags), // 获取private方法"GetString"
mi2 = t.GetMethod( " GetHello " ); // 获取public方法"GetHello"
object obj = a.CreateInstance( " InvokeMethod.Program " );
Console.WriteLine(mi.Invoke(obj, null )); // 调用private方法"GetString"
Console.WriteLine(mi2.Invoke(obj, new string [ 1 ] { " ChuJian " })); // 调用public方法"GetHello"
Console.ReadKey();
}
static private string GetString()
{
return " Some String From Assembly. " ;
}
public string GetHello( string name)
{
return string .Format( " Hello {0}! " , name);
}
}
}