使用反射

 

                                                                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编写的Dllexe文件。

if (openFileDialog1.ShowDialog() == DialogResult.OK)

            {

                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,得到结果,如下图所示:

 

 

二、使用程序集成员

程序集成员包括很多种类型,如字段、方法、事件。这里,我仅以调用程序集中的方法为例来演示使用程序集成员的一般方法。

说到调用方法,有人问:我们不使用反射也可以调用程序集元数据中的方法呀!我可以对相关的dllexe文件引用进项目,然后不就可以调用其中的方法了吗?

是的,一般情况下,使用“引用”来使用其中的方法是可以的,不过使用反射,有一定优势的。比如说,一般情况下,我们只通过“引用”的方法,是无法使用程序集中的私有方法的,而只能使用已经公开的方法或其他成员(据我目前的使用经验是这样的,高手们有不同见解,敬请指教)。可以使用反射,就可以使用程序集中所有你需要的成员。

本例原理:首先是根据用户输入的程序集路径建立一个Assemply对象,然后,获取方法所在的类的类型。而后创建两个MethodInfo对象,用于存放获取的两个方法。

另外,为了调用程序集中的私有方法,本例同样使用了BindingFlags枚举。

再次说明一下,为了测试方便和易于理解,本例所调用的程序集就是本例生成的exe文件,所调用的程序集路径和调用的方法,我已经写到代码中了。如果你想把此例变得通用,可以让程序接受输入的路径和调用的方法的名称。

代码的其他说明,我已经写到注释中了,大家有什么别的问题,可以在此提问。

我测试的运行结果如下图所示:

 

现给出本例代码如下:

using System;

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);

        }

 

    }

}
posted @ 2009-02-17 14:34  褚一剑  阅读(1866)  评论(2编辑  收藏  举报