从 .NET程序集中提取信息

2016-06-14

 我如何才能从一个特定的程序集中得到其包含的所有方法、数据属性和事件的一个列表?比如,我需要知道有关 Windows Form和ADO.NET的这类信息。更进一步地还想得到某个数据属性的数据类型和某个函数的返回值类型。同时还希望以电子表格的形式显示这些信息。

这是一个有趣的问题。看看我作为答案的这个应用程序吧,我会为你解释相关代码的。Figure 1是程序运行时对应于System.Windows.Forms程序集的主界面。它显示了共有28,932个成员被处理。我可并不想手工地去浏览整个列表!

Figure 1 程序集信息阅读器

Figure 2显示了一个程序集中所有方法、数据属性和事件的详细列表。这是你所期望的ComboBox控制的相关内容。正如Figure 2中File菜单所示,你可以将这个列表保存入一个以逗号作为值间分隔符的文件中。然后你可以将其导入Microsoft? Excel的表格中或者其它的什么程序。当然你也可以考虑按XML形式导出这些数据,我也许会在后续的版本中加入这个功能。

Figure 2 另存为以逗号分割的值

第一个 form (frmMain)的界面很简单,它由一个主菜单和几个标签组成。主菜单包含两个顶层子菜单File和View。File菜单包含菜单项:Open Assembly, Open Assembly in GAC和Exit。View菜单只包括一个子菜单:View Assembly Items。
首先,我要演示如何使用 GAC(Global Assembly Cache,全局程序集缓存)内程序集的反射特性。我利用mAssemblyName这个全路径名装载了一个程序集:

      mAssembly = [Assembly].LoadFrom(mAssemblyName)      

  一旦该程序集完成载入,我便可以通过mAssembly检查它的所有类型信息。我试着对GAC中的程序集进行了检查。我认为应该使用Load而不是LoadFrom。到目前为止,一切都还不错。因为Load方法使用了程序集的显示名(完全限定名),这样我就可以用上我在GAC浏览器中看到的这些程序集名了。

Figure 3 GAC 阅读器

我想你已经看到了图中那个被高亮显示的 Microsoft .NET Framework version 1.1中的Windows.Forms类了吧。你是不是在想我把“System.Windows.Forms”这个名字作为参数传递给Load就行了?如果这样想,就大错特错了!对于GAC中的程序集而言,必须使用程序集的完全限定名――包括程序集的版本、区域性(Culture)和公钥(Public Key Token)。因此我需要在命令提示符方式下获得更多的信息。(见Figure 4)。

Figure 4 Windows 窗体目录

Figure 4显示了Windows Forms程序集的GAC路径。请注意这是个完整的目录路径。你可以在获得它之后,将这个全路径名传递给LoadFrom。示例:

C:\WINNT\assembly\GAC\System.Windows.Forms\1.0.5000.0__b77a5c561934e089\System.Windows.Forms.dll      
  但这种形式的全路径名并不是我真正希望采用的方式,因为 GAC文件夹的位置在不同用户的机器上并不一定相同。因此我决定找出其真正的显示名,它应该是这个样子的:
System.Windows.Forms, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089      

很显然,这样的强命名程序集显示名才更有意义。

无论如何,现在我已经知道完整的名字应该是什么样子的了,我也应该允许用户选择它们。但面临的问题是:我如何才能获得 GAC中所有程序集的显示名呢?我可以使用几种很酷的方法,比如通过interop访问非受控的Fusion API,但当下我需要以更快的方式获得这些名字,因此我使用了一种简单的方法。我只是简单地打开了 Visual Studio 的命令提示符方式,然后运行了GACUtil.exe:

      Gacutil /l > gaclist.txt       
  这样就产生了一个包括所有 GAC中程序集的列表文件gaclist.txt。打开这个文本文件,去掉了除显示名之外的头尾信息。然后再删除所有的制表符、空格,并将这个文件保存在了应用程序的Bin目录中。之后,我再利用 Figure 5 中所示的代码将处理完的这个文件装载入全局性的dtGACAssemblies数据表中。这样,frmGACAssemblies.vb中的这段代码将把所有程序集的显示名装载入一个DataGrid中,以允许用户选择要查看的程序集。
  接下来,让我们看看如何从程序集中提取信息。对此,我使用了之前我曾在 2003年2月的专栏中使用过的同一种方法(参见 Figure 6 )。LoadClassesMemberInfo方法需要两个参数:AssemblyFullFileName――程序集的文件名或显示名,如果使用了显示名,则第二个参数需要设为True。这第二个参数决定了在方法的开始部分使用Load还是LoadFrom:
If Not UseAsDisplayName Then 
	mAssembly = [Assembly].LoadFrom(mAssemblyName) 
Else 
	mAssembly = [Assembly].Load(mAssemblyName) 
End If 
  这样应用程序已经装入一个程序集,你可以开始通过反射来获取它的信息了。
现在让我们来分析这个程序集。我使用了一个 For Next循环来枚举该程序集所有可导出的类型(只有public类可被导出)。在循环体内,它将一个当前类型t的引用作为参数反复调用GetMemberInfo方法(见 Figure 7 )。在 Figure 7 所示代码的开始处,调用了GetMembers方法以获取该类型的所有成员,并将这些信息返回到一个MemberInfo类型的数组中保存。
      lMemberInfo = t.GetMembers((BindingFlags.Public Or BindingFlags.Instance 
	Or BindingFlags.InvokeMethod)) 

  现在你可以通过遍历这个数组来处理每个成员的信息了。你认为这就全部完成了是吧?哈哈,还没这么快。

我还想提取每个成员的数据类型 DataType。虽然可以通过由MemberInfo类型向其派生类型的强制类型转换获得该数据类型信息,但我采用了另一种方法。首先在处理成员的循环体内,我使用If语句块过滤了方法、数据属性和事件,这样只有域和构造子这样的成员被保留。然后我再增加了对应于方法、数据属性和事件的三个独立的For Each循环。

每个成员的类型信息均对应于 MemberInfo的一个派生类:

MethodsMethodInfo

EventsEventInfo

PropertiesPropertyInfo

上述的每个类均为对应的成员暴露了对应的属性,并提供了访问其元数据的能力。因此我得以通过调用Type类的GetMembers方法返回某个类型的所有成员,调用Type类的GetMethods、GetProperties和GetEvents方法获得对应的信息类对象数组。接下来,增加三个独立的For Each循环将这些从GetMemberInfo返回的内容分别添加到数据表中。我还需要获取每个方法的参数信息,这其实也是很容易的。只要你看一下前述 2003年2月 专栏里的那些代码,就会明白它是如何实现的了。
  下载包里的代码其实很简单,因此还是让你自己慢慢看吧。请留意frmGACAssemblies.vb中对MouseUp事件的处理。我在这个事件函数中处理DataGrid中的单元选择。尽管还有更好的地方来处理MouseUp事件,比如Click事件,但MouseUp事件却能为执行的动作提供更多额外的信息。

如果你有疑问或者批评,请发到 basics@microsoft.com

Ken Spencer 为32X Tech ( http://www.32X.com )工作,他提供与Microsoft技术相关的培训、软件开发和咨询服务。

本文出自 MSDN Magazine 的 March 2004 期刊,可通过当地 报摊获得,或者最好是 订阅