打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
.NET程序员再谈应用反射,动态编译,代码生成器
userphoto

2013.03.04

关注

工作五年的.NET程序员再谈应用反射,动态编译,代码生成器

工作多年,一直是.NET实践派的代表,没有过多的空洞理论,只谈实作技巧,把技术,知识,工具结合起来,给自己的生活多一点改变,提高产生效率。前面一个话题,《工作多年后才明白的.NET底层开发技术》都觉得反射的例子太过于肤浅,不过瘾,那么这个题材的反射例子,可能不会让你失望。
这个主题来源于我开发带智能提示的模板编辑器的过程,请看下面的例子程序。先说目的,我要达到在模板中敲入Math变量时,可以智能的显示它的成员列表,也就是做一个带智能提示的模板编辑器。

模板的语法定义如下
<%@ Property Name="Math" Type="MathProgram"  Category="Text"Description="Namespace for this class" %>
<%@ Property Name="IncludeDelete" Type="System.Int32" Default="123" Category="Options" Description="If true delete statements will be generated." %>

<%@ Assembly Name="TestClassLibrary" %>
<%@ Import Namespace="EPN.Common" %>

如果你不熟悉这种语法,可以参考Code Smith的入门教程,也可以参考我写的文章《ORM框架-工具-产品开发之四 开发代码生成器 Template Studio Development 》,有二篇。

用简洁的语言来解释这段代码的意思,定义一个变量MathProgram类型的变量Math,它定义于TestClassLibrary程序集的EPN.Common命名空间中。类型MathProgram的代码定义如下

[TypeConverter(typeof(ExpandConverter))] 
public class MathProgram 
{
       public MathProgram(string system,string application)
       {
           _SystemName = system;
           _ApplictionName = application;
       }

       public MathProgram(){}

       public int Add(int a,int b)
       {
           return a + b;
       }

       public int Substract(int a, int b)
       {
           return a - b;
       }

       public static  string Product = "Template Studio";

       private string _SystemName; 
    [Browsable(true)] [Category("Text")]  [DefaultValue("")][Description("Namespace for this class")]
       public string SystemName
       {
           get { return _SystemName; }
           set { _SystemName = value; }
       }

       private string _ApplictionName;

       [Browsable(true)][Category("Text")][DefaultValue("")] [Description("Namespace for this class")]
       public string ApplictionName
       {
           get { return _ApplictionName; }
           set { _ApplictionName = value; }
       }

}

我要达到的效果是,在模板中敲入Math变量时,可以智能的显示它的成员列表。

为了表示这项技术的可行性,我来展示一下,在Code Smith中达到的效果

这项技术是Code Smith的卖点之一,带智能提示的好用的模板编辑器。

到此,你可能对这项技术开始有此好奇了,它是如何做到的,我是如何实现这个功能的,下面讲解它的原理和实现代码。

 

.NET编辑器,对点号.非常的在意,C++则同时有三个符号.,->,::都可以表示成员调用。当敲入点符号后,编辑器会启动分析程序,对点号前面的token进行分析,找到它的类型。如果是类型定义,则会列出它的成员,这是Visual Studio很平常但又很重要的功能。这个功能已经有很多编辑器实现了,直接调用即可。

但是,到了模板编辑器这里,又有些特殊,因为我们平时是这样来写代码的

MathProgram  Math=new MathProgram();

Math.SystemName ; 这个SystemName变量可以根据智能提示显示出来

到了模板编辑器里面,代码定义是这样的

<%@ Property Name="Math" Type="MathProgram"  Category="Text"Description="Namespace for this class" %>
这句话,也等同于代码片段MathProgram Math=new MathProgram();

当用户敲完了Property 的%>之后,分析器在后台应该加上了代码片段MathProgram Math=new MathProgram();


.NET 2.0后支持partial关键字,允许把类型的定义放在多个源代码文件中,编译时自动合并代码。我将模板中的模板内容,解析成一个C#代码文件,类型名字就取自模板名。这样,每次模板有变化,后台进程会重新分析模板的内容,重新生成C#代码文件,以用于成员的定义。似乎智能提示的问题到此应该可以解决了。

可是不行,达不到目的,C#中不允许有全局变量,也就是在一个地方定义,在另一个地方调用。我可以在后台C#代码中定义一个变量,但是在模板文件中取不到这个变量,.NET是OOP的语言,不允许全局有变量。


再来设想另一种情况,当我们敲入一个类型名,再敲点号,它会立即显示它的static成员,这个特性帮忙我解决了问题。

再来回顾一下模板的属性定义语法,类型MathProgram,变量名称是Math

<%@ Property Name="Math" Type="MathProgram"  Category="Text"Description="Namespace for this class" %>

当输入完这段语法后,我在后台构造一个Math类型,而不是变量(因为C#不允许全局变量), 当用户在模板文件中敲入Math属性时,它会取自我在后台定义的类型,至于要显示它的成员,那很简单,把MathProgram中的成员,追加到我的后台定义类型Math中即可,并且加上static属性,这样,我在敲模板属性Math之后的点,就相当于在调用我后台生成的Math类型,它的方法做取自MathProgram的instance方法和属性。

 

问题到了这里,需要应用反射。我需要根据程序集TestClassLibrary中的类型EPN.Common.MathProgram来构造它的源码,以用于我的编辑器智能提示。根据程序集来构造它的源代码,代码看起来是这样的

string nameSpace = "EPN.Common";
string typeName = "MathProgram";
string fullName = nameSpace + "." + typeName;
string instanceName = "Math";

string assemblyName=”TestClassLibrary”;
string source = CodeDomHelper.BuildSpecifiedTypeSourceCode(assemblyName,fullName, instanceName);

读到这里,你可能会明白Code Smith的模板语法的Property 的含义了。我没有机会读Code Smith的源代码,但是我这几行代码足够帮忙你深刻领悟它的含义,知其所以然。

 

再来看如何根据程序集,产生它的源代码。我不是指用Reflector这样的工具来产生源代码,在我的Template Studio中,根本没有机会这样做,用户不可能帮忙你反编译程序集,给出源代码,你再来显示它的成员列表。
第一直觉印象就是command line调用Reflector程序,让它帮忙反编译成源代码,我想到可能是这样的
cmd->Reflector  “TestLibrary”  "EPN.Common.MathProgram”  "C:\DNA\Math.cs”
用语言来表达是,我希望传入程序集,需要反编译的类型,然后到指定的文件中取它的源代码。
可惜,暂时Reflector做不到这样,官方已经把这作为一个feature,但是什么时候实现不得而知。

另一个很不错的反编译器ILSpy,开放源码,但是也没有看到有command line调用的例子,又失败。
第三个想法是,写Reflector的plug-in,已经有Reflector.FileDisassembler.dll,通过这个组件,你可以一次性的把一个程序集导出为.NET源代码,Reflector7内置了这个组件,请看图


于是,我想到查看Reflector的Export Assembly Source Code菜单命令的源代码,可是你知道的,用Reflector读它自己的源代码,这不可行。人家把工具卖给你是让你对付别人的程序集的,而不是用在它自己身上。

真到了这一步,你会发现互联网的资料真是不多,这些资料,永远都不会公开的,看来还是只有自己靠自己,要么变通一下,要么放弃这个feature的实现。

再来继续看问题,我的智能编辑器需要的是EPN.Common.MathProgram类型的源代码,但也不需要它的完整源代码,只拿到代码的签名就可以了,具体来说,属性定义,方法只需要签名,就达到目的了。因为我只用它来作为智能提示的内容,不需要执行它,也就不需要代码内容,body也可忽略。所以,我需要下面的这段代码

public class Math
   {
      public  static int Add(int a,int b)
      {
          return 2012;
      }

      public static int Substract(int a, int b)
      {
          return 2012;
      }

      public static string SystemName
      {
          get { return “2012”; }
          set {   }
      }      

       public static string ApplictionName
      {
          get { “2012”; }
          set { }
      }

}

看到在代码中,我把属性生成为static类型的,方法体都是空,当你在Visual Studio的代码编辑器中敲入Math类型时,它会智能的列出以上的成员,因为这里都是static型的。再对比一下模板的定义,我们把模板中的属性名称Math也当成了这里的Math类型,也就达到Code Smith中,敲入变量Math后,带有智能提示的效果


再来分析代码生成技术,我想,你肯定对这段代码感兴趣了

string source = CodeDomHelper.BuildSpecifiedTypeSourceCode(assemblyName,fullName, instanceName);

是的,这就是代码生成技术,根据程序集生成它的源代码。看名字CodeDom,可能已经猜到了它的实现。

我确实是用CodeDom技术来实现的,因为要同时支持VB和C#两种代码,CodeDom是最好的技术选择。这样,我的模板才可以同时,支持两种语法C#和VB语法,后台的CodeDom帮忙我生成VB和C#两种语法的源代码文件,以用于智能提示。其实,我一开始没有考虑用CodeDom做代码生成的工具,因为它的语法有些难看,不容易理解,我一开始用C#拼凑字符串,生成C#的源代码文件,然后调用下面的工具,直接转化成VB的语法

Code Conversion是以前写的一个C#与VB代码相互转换的工具,因为有源代码,所以用代码转换C#到VB和用Code Conversion工具转换代码没有区别。辛苦的启动这个软件,单击了几个菜单,我用代码调用,一下子就把C#代码全部做转化成了VB代码,这就是代码的魔力。

回顾这个过程,C#代码,编译成程序集,添加到模板中应用;模板编辑器后台程序分析程序集的类型定义,生成它的源代码骨架,用于智能提示窗体的成员列表显示;为了支持VB和C#两种语法,生成代码的方式选择CodeDom技术。

 

总结:我承认我隐藏了很多实现的细节,既然我已经做出了这个效果,我可以自信的说,它应用了大量的反射,动态编译和CodeDom代码生技术。写文章的思路是断开的,要分重点的讲一下各个实现要点,但又要能合起来,看成一个整体,起承转合,上下衔接,这样才可以透彻的理解。我现在理解你迫切需要那几个C#文件,来把这篇文章的代码和思路连接成整体。等《ORM框架-工具-产品开发》系列的文章陆续发布后,可能有机会看到这些代码的实现。

11
1
(请您对文章做出评价)
博主上一篇:ORM框架-工具-产品开发之四 开发代码生成器 Template Studio Development (二)
博主下一篇:Reflector 已经out了,试试ILSpy
首页上一篇:通过javascript操作CSS3属性实现动画
首页下一篇:WCF常见问题及解决方案
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ILSpy 反编译工具 介绍
C# 反编译神器之ILSpy
jqGrid获取本地查询后匹配的所有数据
C加加模板之类模板的特化
在PHP模板引擎smarty生成随机数的方法和math函数详解
Android NDK开发入门实例
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服