首页 > 开发 > 综合 > 正文

CodeDom:语言的界限在这里消失

2024-07-21 02:29:35
字体:
来源:转载
供稿:网友


  .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 初始化名称空间
  
    初始化名称空间的代码类似下面这种形式:
  
  private codenamespace initializenamespace(string name)
  {
   // 初始化codenamespace变量,指定名称空间的名称
   codenamespace currentnamespace = new codenamespace (name);
   // 将一些名称空间加入到要导入的名称空间集合。
   // 各种语言如何导入名称空间的细节由每种语言对应
   // 的codeprovider分别处理。
   currentnamespace.imports.add (new codenamespaceimport("system"));
   currentnamespace.imports.add (new codenamespaceimport("system.text"));
   return currentnamespace;
  }
  
  
  
    这段代码定义了一个新的名称空间,并导入system和system.text名称空间。
  
    1.2 创建类
  
    声明一个新类的代码类似下面这种形式:
  
  private codetypedeclaration createclass (string name)
  {
   // 新建一个codetypedeclaration对象,指定要创建的类的名称
   codetypedeclaration ctd = new codetypedeclaration (name);
   // 指定这个codetype是一个类,而不是一个枚举变量或struct
   ctd.isclass = true;
   // 这个类的访问类型是public
   ctd.attributes = memberattributes.public;
   // 返回新创建的类
   return ctd;
  }
  
  
  
    createclass函数新建一个指定名称的类,做好为该类植入方法、属性、事件的准备。
  
    1.3 创建方法
  
    声明一个新函数的代码类似下面这种形式:
  
  private codeentrypointmethod createmethod()
  {
   // 创建一个方法
   codeentrypointmethod method = new codeentrypointmethod();
   // 指定该方法的修饰符:public,static
   method.attributes = memberattributes.public |
   memberattributes.static;
   // 返回新创建的方法
   return method;
  } 
   
  本例创建了一个codeentrypointmethod对象。codeentrypointmethod对象类似于codemembermethod对象,两者的不同之处在于,codeprovider会将codeentrypointmethod代表的方法作为类的入口点调用,例如作为sub main或void main等。对于codeentrypointmethod对象,方法的名称默认为main;对于codemembermethod,方法的名称必须显式指定。
  
    1.4 声明变量
  
    声明一个变量的代码类似下面这种形式:
  
  private codevariabledeclarationstatement
   declarevariables(system.type datatype,
   string name)
  {
   // 为将要创建的变量类型创建一个codetypereference对象,
   // 这使得我们不必去关注该类数据在特定语言环境中的
   // 与数据类型有关的细节问题。
   codetypereference tr = new codetypereference (datatype );
   // codevariabledeclarationstatement对象使得我们不必纠缠于
   // 与特定语言有关的下列细节:在该语言的变量声明语句中,
   // 应该是数据类型在前,还是变量名称在前;声明变量时是
   // 否要用到dim之类的关键词.
   codevariabledeclarationstatement declaration =
   new codevariabledeclarationstatement(tr, name);
   // codeobjectcreateexpression负责处理所有调用构造器的细节。
   // 大多数情况下应该是new,但有时要使用new。但不管怎样,
   // 我们不必去关注这些由语言类型决定的细节.
   codeobjectcreateexpression newstatement = new
   codeobjectcreateexpression ();
   // 指定我们要调用其构造器的对象.
   newstatement.createtype = tr;
   // 变量将通过调用其构造器的方式初始化.
   declaration.initexpression = newstatement;
   return declaration;
  } 
         
  每一种.net语言都有其特定的数据类型名称,所有这些数据类型都被映射到公共的.net语言类型。例如,对于c#中称为int的数据类型,在vb.net中是integer,公共的.net类型是system.int32。codetypereference对象直接使用.net公共数据类型,以后由每种语言的codeprovider将它转换成符合各自语言规范的类型名称。
  
    1.5 初始化数组
  
    初始化一个数组的代码类似下面这种形式:
  
  private void initializearray (string name,
   params char[] characters )
  {
   // 从参数中传入的字符数组获得一个codetypereference 对象,
   // 以便在生成的代码中复制该数据类型.
   codetypereference tr = new codetypereference (characters.gettype());
   // 声明一个匹配原始数组的数组
   codevariabledeclarationstatement declaration =
   new codevariabledeclarationstatement (tr, name);
   // codeprimitiveexpression代表“基本”或值数据类型,
   // 例如char、int、double等等。
   // 我们将用这类基本数据类型构成的一个数组来
   // 初始化我们正在声明的数组。
   codeprimitiveexpression[] cpe = new
   codeprimitiveexpression[characters.length];
   // 循环遍历原始字符数组,
   // 为codeprimitiveexpression类型的数组创建对象。
   for (int i = 0; i < name.length ; i++)
   {
   // 每一个codeprimitiveexpression将有一个字符的语言
   // 中立的表示。
   cpe[i] = new codeprimitiveexpression (characters[i]);
   }
   // codearraycreateexpression负责调用数组中数据类型的
   // 默认构造器。
   // 由于我们还传入了一个codeprimitiveexpression的数组,
   // 所以不必指定数组的大小,且数组中的每一个元素都将有
   // 合适的初值。
   codearraycreateexpression array = new
   codearraycreateexpression(tr, cpe);
   // 指定:该codearraycreateexpression将初始化数组变量声明。
   declaration.initexpression = array;
   return declaration;
  }
  
  
  
    1.6 定义循环结构
  
    声明一个循环结构的代码类似下面这种形式:
  
  private codeiterationstatement createloop(string loopcontrolvariablename)
  {
   // 声明一个新的变量,该变量将作为
   // 循环控制变量
   codevariabledeclarationstatement declaration;
   // 声明一个管理所有循环逻辑的codeiterationstatement
   codeiterationstatement forloop = new codeiterationstatement();
   // 为动态声明的变量指定数据类型的另一种方法:
   // 用typeof函数获得该数据类型的type对象,不必
   // 用到该类数据的变量
   declaration = new codevariabledeclarationstatement(typeof (int),
   loopcontrolvariablename);
   // 指定一个简单的初始化表达式:
   // 将新变量设置为0
   declaration.initexpression = new codesnippetexpression ("0");
   // 这个新声明的变量将用来初始化循环
   forloop.initstatement = declaration;
   // codeassignstatement用来处理赋值语句。
   // 这里使用的构造器要求提供两个表达式,第一个位于
   // 赋值语句的左边,第二个位于赋值语句的右边。
   // 另一种办法是:调用默认的构造器,然后分别显式设置
   // 左、右两个表达式。
   codeassignstatement assignment = new codeassignstatement(
   new codevariablereferenceexpression(loopcontrolvariablename),
   new codesnippetexpression (loopcontrolvariablename + " + 1" ));
   // 在循环迭代中使用赋值语句。
   forloop.incrementstatement = assignment;
   // 当循环控制变量超出数组中的字符个数时,
   // 循环结束
   forloop.testexpression = new codesnippetexpression
   (loopcontrolvariablename + " < characters.length");
   return forloop;
  } 
   
   
   
  注意,这里我们用typeof函数直接获得循环控制变量的数据类型的type对象,而不是通过声明一个codetypereference对象的方式。这是codevariabledeclartionstatement的又一个构造器,实际上其构造器的总数多达7种。 
   
  1.7 索引数组 
   
  索引一个数组的代码类似下面这种形式:
  
  private codearrayindexerexpression
   createarrayindex(string arrayname, string indexvalue )
  {
   // 新建一个codearrayindexerexpression
   codearrayindexerexpression index = new codearrayindexerexpression ();
   // indices属性是一个能够支持多维数组的集合。不过这里我们只需要
   // 一个简单的一维数组。
   index.indices.add ( new codevariablereferenceexpression (indexvalue));
   // targetobject指定了要索引的数组的名称。
   index.targetobject = new codesnippetexpression (arrayname);
   return index;
  } 
   
   
   
  codearrayindexerexpression对象处理数组索引方式的种种差异。例如,在c#中数组以arrayname[indexvalue]的方式索引;但在vb.net中,数组以arrayname(indexvalue)的方式索引。

  codearrayindexerexpression允许我们忽略这种差异,将注意力集中到其他更重要的问题,例如要索引哪一个数组、要访问第几个数组元素。 
   
  二、装配出树结构 
   
  我们可以把前面定义的所有函数加入到一个类,通过构造器初始化,例如:
  
  public codedomprovider()
  {
   currentnamespace = initializenamespace("testspace");
   codetypedeclaration ctd = createclass ("helloworld");
   // 把类加入到名称空间
   currentnamespace.types.add (ctd);
   codeentrypointmethod mtd = createmethod();
   // 把方法加入到类
   ctd.members.add (mtd);
   codevariabledeclarationstatement variabledeclaration =
   declarevariables (typeof (stringbuilder), "sbmessage");
   // 把变量声明加入到方法
   mtd.statements.add (variabledeclaration);
   codevariabledeclarationstatement array = initializearray
   ("characters", 'h', 'e', 'l', 'l', 'o', ' ',
   'w', 'o', 'r', 'l', 'd');
   // 把数组加入到方法
   mtd.statements.add (array);
   codeiterationstatement loop = createloop("intcharacterindex");
   // 把循环加入到方法
   mtd.statements.add (loop);
   // 数组索引
   codearrayindexerexpression index = createarrayindex("characters",
   "intcharacterindex");
   // 加入一个语句,它将调用sbmessage对象的“append”方法
   loop.statements.add (new codemethodinvokeexpression (
   new codesnippetexpression ("sbmessage"),"append",
   index));
   // 循环结束后,输出所有字符追加到sbmessage对象
   // 后得到的结果
   mtd.statements.add (new codesnippetexpression
   ("console.writeline (sbmessage.tostring())"));
  } 
   
   
   
  构造器的运行结果是一个完整的codedom树结构。可以看到,至此为止我们的所有操作都独立于目标语言。最后生成的代码将以属性的形式导出。 
   
  三、输出生成结果 
   
  构造好codedom树之后,我们就可以较为方便地将代码以任意.net语言的形式输出。每一种.net语言都有相应的codeprovider对象,codeprovider对象的creategenerator方法能够返回一个实现了icodegenerator接口的对象。icodegenerator接口定义了用来生成代码的所有方法,而且允许我们定义一个用来简化属性输出的辅助方法。下面的辅助方法generatecode负责设置好合适的textwriter以供输出代码,以字符串的形式返回结果文本。
  
  private string generatecode (icodegenerator codegenerator)
  {
   // codegeneratoroptions允许我们指定各种供代码生成器
   // 使用的格式化选项
   codegeneratoroptions cop = new codegeneratoroptions();
   // 指定格式:花括号的位置
   cop.bracingstyle = "c";
   // 指定格式:代码块的缩进方式
   cop.indentstring = " ";
   // generatecodefromnamespace要求传入一个textwriter以
   // 容纳即将生成的代码。这个textwriter可以是一个streamwriter、
   // 一个stringwriter或一个indentedtextwriter。
   // streamwriter可用来将代码输出到文件。
   // stringwriter可绑定到stringbuilder,后者可作为一个变量引用。
   // 在这里,我们把一个stringwriter绑定到stringbuilder sbcode。
   stringbuilder sbcode = new stringbuilder();
   stringwriter sw = new stringwriter(sbcode);
  
   // 生成代码!
   codegenerator.generatecodefromnamespace(currentnamespace, sw,cop);
   return sbcode.tostring();
  } 
      
    有了这个辅助函数,要获取各种语言的代码就相当简单了:
  
  public string vbcode
  {
   get
   {
   vbcodeprovider provider = new vbcodeprovider ();
   icodegenerator codegen = provider.creategenerator ();
   return generatecode (codegen);
   }
  
  }
  
  public string jscriptcode
  {
   get
   {
   jscriptcodeprovider provider = new jscriptcodeprovider ();
   icodegenerator codegen = provider.creategenerator ();
   return generatecode(codegen);
   }
  
  }
  
  public string jsharpcode
  {
   get
   {
   vjsharpcodeprovider provider = new vjsharpcodeprovider ();
   icodegenerator codegen = provider.creategenerator ();
   return generatecode (codegen);
   }
  
  }
  
  public string csharpcode
  {
   get
   {
   csharpcodeprovider provider = new csharpcodeprovider();
   icodegenerator codegen = provider.creategenerator ();
   return generatorcode (codegen);
   }
  
  } 
   
   
   
  四、显示出生成的代码 
   
  为输出代码,我们要用到一个简单的.aspx文件,它有四个标签,分别对应一种.net语言:
  
  <table width="800" border="1">
   <tr>
   <th>vb.net代码</th>
   </tr>
   <tr >
   <td>
   <asp:label id="vbcode" runat="server" cssclass="code">
   </asp:label>
   </td>
   </tr>
   <tr>
   <th>
   c#代码</th></tr>
   <tr>
   <td><asp:label id="csharpcode" runat="server" cssclass="code">
   </asp:label></td>
   </tr>
   <tr>
   <th>j#代码</th></tr>
   <tr >
   <td>
   <asp:label id="jsharpcode" runat="server" cssclass="code">
   </asp:label>
   </td>
   </tr>
   <tr>
   <th>jscript.net代码</th>
   </tr>
   <tr>
   <td><asp:label id="jscriptcode" runat="server" cssclass="code">
   </asp:label></td>
   </tr>
  </table> 
      
  在后台执行的代码中,我们实例化一个前面创建的codedomprovider类的实例,把它生成的代码赋值给.aspx页面的相应标签的text属性。为了使web页面中显示的代码整齐美观,有必要做一些简单的格式化,替换换行符号、空格等,如下所示:
  
  private string formatcode (string codetoformat)
  {
   string formattedcode = regex.replace (codetoformat, "/n", "<br>");
   formattedcode = regex.replace (formattedcode, " " , " ");
   formattedcode = regex.replace (formattedcode, ",", ", ");
   return formattedcode;
  } 
   
   
   
  下面把生成的代码显示到web页面:
  
  private void page_load(object sender, system.eventargs e)
  {
  
   helloworld.codedomprovider codegen = new helloworld.codedomprovider ();
   vbcode.text = formatcode (codegen.vbcode);
   csharpcode.text = formatcode (codegen.csharpcode);
   jscriptcode.text = formatcode (codegen.jscriptcode);
   jsharpcode.text = formatcode (codegen.jsharpcode);
   page.enableviewstate = false;
  }
  
  
  
    输出结果如下:
  
  vb.net代码
  
  imports system
  imports system.text
  
  namespace helloworld
  
   public class hello_world
  
   public shared sub main()
   dim sbmessage as system.text.stringbuilder = _
   new system.text.stringbuilder
   dim characters() as char = new char() {_
   microsoft.visualbasic.chrw(72), _
   microsoft.visualbasic.chrw(69), _
   microsoft.visualbasic.chrw(76), _
   microsoft.visualbasic.chrw(76), _
   microsoft.visualbasic.chrw(79), _
   microsoft.visualbasic.chrw(32), _
   microsoft.visualbasic.chrw(87), _
   microsoft.visualbasic.chrw(79), _
   microsoft.visualbasic.chrw(82), _
   microsoft.visualbasic.chrw(76), _
   microsoft.visualbasic.chrw(68)}
   dim intcharacterindex as integer = 0
   do while intcharacterindex < characters.length
   sbmessage.append(characters(intcharacterindex))
   intcharacterindex = intcharacterindex + 1
   loop
   console.writeline (sbmessage.tostring())
   end sub
   end class
  end namespace
  
  c#代码
  
  namespace helloworld
  {
   using system;
   using system.text;
  
   public class hello_world
   {
   public static void main()
   {
   system.text.stringbuilder sbmessage = new
   system.text.stringbuilder();
   char[] characters = new char[] {
   'h',
   'e',
   'l',
   'l',
   'o',
   ' ',
   'w',
   'o',
   'r',
   'l',
   'd'};
   for (int intcharacterindex = 0;
   intcharacterindex < characters.length;
   intcharacterindex = intcharacterindex + 1)
   {
   sbmessage.append(characters[intcharacterindex]);
   }
   console.writeline (sbmessage.tostring());
   }
   }
  }
  
  j#代码
  
  package helloworld;
  import system.*;
  import system.text.*;
  
  
  public class hello_world
  {
   public static void main(string[] args)
   {
   system.text.stringbuilder sbmessage = new
   system.text.stringbuilder();
   char[] characters = new char[]
   {
   'h',
   'e',
   'l',
   'l',
   'o',
   ' ',
   'w',
   'o',
   'r',
   'l',
   'd'}
   ;
   for (int intcharacterindex = 0;
   intcharacterindex < characters.length;
   intcharacterindex = intcharacterindex + 1)
   {
   sbmessage.append(characters[intcharacterindex]);
   }
   console.writeline (sbmessage.tostring());
   }
  }
  
  
  jscript.net代码
  
  
  //@cc_on
  //@set @debug(off)
  
  import system;
  import system.text;
  
  package helloworld
  {
  
   public class hello_world
   {
  
   public static function main()
   {
   var sbmessage : system.text.stringbuilder =
   new system.text.stringbuilder();
   var characters : char[] =
   ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'];
   for (var intcharacterindex : int = 0;
   ; intcharacterindex < characters.length;
   intcharacterindex = intcharacterindex + 1)
   {
   sbmessage.append(characters[intcharacterindex]);
   }
   console.writeline (sbmessage.tostring());
   }
   }
  }
  helloworld.hello_world.main(); 
   
  总结:codedom体现了.net中语言的重要性不如框架的思想。本文示范了如何运用一些常用的codedom类,几乎每一个使用codedom技术的应用都要用到这些类。作为一种结构化的代码生成技术,codedom有着无限的潜能,唯一的约束恐怕在于人们的想象力。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表