.NET推崇这样一种思想:相对于框架而言,语言处于从属、次要的地位。CodeDom名称空间中包含的类是这一思想的集中体现。我们可以用CodeDom构造一个树或图,用System.CodeDom名称空间的类填充它,完成后,用对应各种.NET语言的CodeProvider对象将树结构转换成该种语言的代码。要更换一种语言,简单到只需更换一下最后用到的CodeProvider对象。
  
    设想一下,利用这一技术,我们至少能够:
  
    ·查询存储过程的元数据,构造出一个负责参数绑定的类。
  
    ·查询程序集的manifest,构造出一个对每个函数执行单元测试的类。
  
    ·为开发组用到的每一种语言生成样板代码。
  
    ·只需写一份范例代码,就可以让用户自由选择他们要查看哪一种语言的版本。
  
    ·自定义模板语法,经解析后生成任意语言的代码。
  
    ·如果要学习某种不熟悉的语言,可以生成该语言的代码,然后将它与熟悉的语言比较。
  
    一、基本操作
  
    System.CodeDom名称空间包含了许多以语言中立的形式描述常见程序结构的对象,每一种语言的细节则由与该种语言对应的CodeProvider对象负责处理。例如,CodeConditionStatement包含一个TrueStatements集合、一个FalseStatements集合和一个条件属性(Condition attribute),但不涉及条件语句块要用“end if”还是右花括号“}”结束,这部分细节由CodeProvider处理。有了这一层抽象,我们就可以描述待生成的代码结构,然后将它以任意语言的形式输出,却不必斤斤计较于各种与特定语言有关的细节问题。同时,这种抽象也为我们通过程序改变代码的结构带来了方便。例如,当我们发现某个方法需要增加一个参数时,就可以将参数加入到该方法的Parameters集合,根本无须改动已生成的代码逻辑。
  
    我们在本文中要用到的大部分对象来自System.CodeDom名称空间,其余对象主要来自各个与特定语言有关的名称空间,例如Microsoft.CSharp名称空间、Microsoft.VisualBasic名称空间、Microsoft.JScript名称空间和Microsoft.VJSharp名称空间。所有这些面向特定语言的名称空间都包含各自的CodeProvider对象。最后,System.CodeDom.Complier名称空间定义ICodeGenerator接口,后者用来把生成的代码输出到一个TextWriter对象。
  
    如果我们只要生成一些用于插件或宏的代码片断,可以利用CodeGenerator从Statement、Expression、Type等生成代码。反之,如果我们要生成的是一个完整的文件,则必须从CodeNameSpace对象入手。在本文的例子中,我们将从一个名称空间开始,示范如何加入import语句、声明类、声明方法、声明变量、实现一个循环结构、索引一个数组,最后,我们将这些技术结合起来,得到一个大家都熟悉的程序。
  
    1.1 初始化名称空间
  
    初始化名称空间的代码类似下面这种形式: 
   
 1没有语言只有框架  private CodeNameSpace InitializeNameSpace(string Name) 
 2   

   
   
    这段代码定义了一个新的名称空间,并导入System和System.Text名称空间。
  
    1.2 创建类
  
    声明一个新类的代码类似下面这种形式: 
 1没有语言只有框架   
 2没有语言只有框架  private CodeTypeDeclaration CreateClass (string Name) 
 3   

   
   
    CreateClass函数新建一个指定名称的类,做好为该类植入方法、属性、事件的准备。
  
    1.3 创建方法
  
    声明一个新函数的代码类似下面这种形式: 
 1没有语言只有框架   
 2没有语言只有框架  private CodeEntryPointMethod CreateMethod() 
 3   

   
   
    本例创建了一个CodeEntryPointMethod对象。CodeEntryPointMethod对象类似于CodeMemberMethod对象,两者的不同之处在于,CodeProvider会将CodeEntryPointMethod代表的方法作为类的入口点调用,例如作为Sub Main或void main等。对于CodeEntryPointMethod对象,方法的名称默认为Main;对于CodeMemberMethod,方法的名称必须显式指定。
  
    1.4 声明变量
  
    声明一个变量的代码类似下面这种形式: 
   
 
 1没有语言只有框架 private CodeVariableDeclarationStatement 
 2没有语言只有框架   DeclareVariables(System.Type DataType, 
 3没有语言只有框架   string Name) 
 4   

   
   
    每一种.NET语言都有其特定的数据类型名称,所有这些数据类型都被映射到公共的.NET语言类型。例如,对于C#中称为int的数据类型,在VB.NET中是Integer,公共的.NET类型是System.Int32。CodeTypeReference对象直接使用.NET公共数据类型,以后由每种语言的CodeProvider将它转换成符合各自语言规范的类型名称。
  
    1.5 初始化数组
  
    初始化一个数组的代码类似下面这种形式: 
   
 
 1没有语言只有框架 private void InitializeArray (string Name, 
 2没有语言只有框架   params char[] Characters ) 
 3
   
   
   
    1.6 定义循环结构
  
    声明一个循环结构的代码类似下面这种形式: 
   
 
 1没有语言只有框架 private CodeIterationStatement CreateLoop(string LoopControlVariableName) 
 2
   
   
    注意,这里我们用typeof函数直接获得循环控制变量的数据类型的Type对象,而不是通过声明一个CodeTypeReference对象的方式。这是CodeVariableDeclartionStatement的又一个构造器,实际上其构造器的总数多达7种。
  
    1.7 索引数组
  
    索引一个数组的代码类似下面这种形式: 
 1没有语言只有框架   
 2没有语言只有框架  private CodeArrayIndexerExpression 
 3没有语言只有框架   CreateArrayIndex(string ArrayName, string IndexValue ) 
 4   

   
   
    CodeArrayIndexerExpression对象处理数组索引方式的种种差异。例如,在C#中数组以ArrayName[IndexValue]的方式索引;但在VB.NET中,数组以ArrayName(IndexValue)的方式索引。CodeArrayIndexerExpression允许我们忽略这种差异,将注意力集中到其他更重要的问题,例如要索引哪一个数组、要访问第几个数组元素。
  
    二、装配出树结构
  
    我们可以把前面定义的所有函数加入到一个类,通过构造器初始化,例如: 
   
 
 1没有语言只有框架 public CodeDomProvider() 
 2
   
   
   
    构造器的运行结果是一个完整的CodeDom树结构。可以看到,至此为止我们的所有操作都独立于目标语言。最后生成的代码将以属性的形式导出。
  
    三、输出生成结果
  
    构造好CodeDom树之后,我们就可以较为方便地将代码以任意.NET语言的形式输出。每一种.NET语言都有相应的CodeProvider对象,CodeProvider对象的CreateGenerator方法能够返回一个实现了ICodeGenerator接口的对象。ICodeGenerator接口定义了用来生成代码的所有方法,而且允许我们定义一个用来简化属性输出的辅助方法。下面的辅助方法GenerateCode负责设置好合适的TextWriter以供输出代码,以字符串的形式返回结果文本。 
   
 
 1没有语言只有框架 private string GenerateCode (ICodeGenerator CodeGenerator) 
 2
   
   
   
    有了这个辅助函数,要获取各种语言的代码就相当简单了: 
   
 
 1没有语言只有框架 public string VBCode 
 2
   
   
   
    四、显示出生成的代码
  
    为输出代码,我们要用到一个简单的.aspx文件,它有四个标签,分别对应一种.NET语言: 
   
 
 1没有语言只有框架 <table width="800" border="1"> 
 2没有语言只有框架   <tr> 
 3没有语言只有框架   <th>VB.NET代码</th> 
 4没有语言只有框架   </tr> 
 5没有语言只有框架   <tr > 
 6没有语言只有框架   <td> 
 7没有语言只有框架   <asp:Label ID="vbCode" Runat="server" CssClass="code"> 
 8没有语言只有框架   </asp:Label> 
 9没有语言只有框架   </td> 
10没有语言只有框架   </tr> 
11没有语言只有框架   <tr> 
12没有语言只有框架   <th> 
13没有语言只有框架   C#代码</th></tr> 
14没有语言只有框架   <tr> 
15没有语言只有框架   <td><asp:Label ID="csharpcode" Runat="server" CssClass="code"> 
16没有语言只有框架   </asp:Label></td> 
17没有语言只有框架   </tr> 
18没有语言只有框架   <tr> 
19没有语言只有框架   <th>J#代码</th></tr> 
20没有语言只有框架   <tr > 
21没有语言只有框架   <td> 
22没有语言只有框架   <asp:Label ID="JSharpCode" Runat="server" CssClass="code"> 
23没有语言只有框架   </asp:Label> 
24没有语言只有框架   </td> 
25没有语言只有框架   </tr> 
26没有语言只有框架   <tr> 
27没有语言只有框架   <th>JScript.NET代码</th> 
28没有语言只有框架   </tr> 
29没有语言只有框架   <tr> 
30没有语言只有框架   <td><asp:Label ID="JScriptCode" Runat="server" CssClass="code"> 
31没有语言只有框架   </asp:Label></td> 
32没有语言只有框架   </tr> 
33没有语言只有框架  </table> 
34没有语言只有框架
   
   
   
    在后台执行的代码中,我们实例化一个前面创建的CodeDomProvider类的实例,把它生成的代码赋值给.aspx页面的相应标签的Text属性。为了使Web页面中显示的代码整齐美观,有必要做一些简单的格式化,替换换行符号、空格等,如下所示: 
1没有语言只有框架   
2没有语言只有框架  private string FormatCode (string CodeToFormat) 
3   

   
   
    下面把生成的代码显示到Web页面: 
   
  1没有语言只有框架
  2没有语言只有框架  private void Page_Load(object sender, System.EventArgs e) 
  3
   
   
   
    总结:CodeDom体现了.NET中语言的重要性不如框架的思想。本文示范了如何运用一些常用的CodeDom类,几乎每一个使用CodeDom技术的应用都要用到这些类。作为一种结构化的代码生成技术,CodeDom有着无限的潜能,唯一的约束恐怕在于人们的想象力。

相关文章: