摘要:CodeDOM是.net framework的一项重要的源代码生成技术。本文详细讨论了CodeDOM的原理以及如何利用CodeDOM技术实现一个与语言无关的Code Wizard。并给出了一个用C#语言实现的例子。
关键字:Code Wizards、CodeDOM、.net framework、数据表、模板文件
一、什么是CodeDom?
现在的程序规模越来越大,虽然在计算发展的几十年间,产生了许多快捷、高效的编程语言和开发工具,如C#、Visual Studio、java等。也产生了许多用以辅助软件设计、开发的思想和方法,如UML、OOP、Agile等。尽管利用这些技术和方法可以大大提高程序编写的效率,但是仍可能有重复的编码工作。因此,现在出现了许多可以自动产生源代码或者目标文件的软件,即Code Wizards。
一般这些Code Wizards在生成源代码时都是通过设置模板文件,然后根据这些模板文件生成源代码。有很多Code Wizards只能生成固定的语言(如java、C#等)。虽然有一些Code Wizards可以生成多种语言,但也只是固定的几种。而且生成源代码部分都是显示地固定在程序中。这样非常不易扩展。如CodeSmith系统,这是一个非常不错的Code Wizard。它使用一个扩展名为cst的文件来设置模板。这个模板文件的格式类似于Asp.net。如果想生成C#源代码,必须要在其中显示地标明,并且模板的固定部分要使用C#语言编写。如果这样的话,同样功能要生成不同语言的代码,如C#和VB.net。就要编写两个模板文件。这是非常不方便的。
从以上的描述来看, Code Wizard所面临的一个重要问题就是如何使用一个模版文件来生成不同语言的源代码。幸好Microsoft提供了一种解决方案,这就是CodeDOM技术。CodeDOM的全称是代码文档对象模型(Code Document Object Model)。整个CodeDOM就是一张对象图(object graph)。它用这张图中的所有对象描述了面向对象语言中的几乎所有的语法现象,如类、接口、方法、属性等。CodeDOM通过对象模型对语言进行了抽象,然后利用具体语言所提供的生成源代码的机制来生成源代码,并可调用相应的编译器将源码生成*.dll或*.exe。从而可以达到与语言无关的目的。图1描述了使用CodeDOM生成和编译源代码的过程。
图1 CodeDom生成和编译源代码的过程
从上图可以看出,CodeWizard只使用CodeDOM对语言进行抽象,然后通过CodeDomProvider生成源代码。最后通过编译器生成中间语言。下面将详细讨论如何利用CodeDOM来实现CodeWizard。
二、实现CodeWizard
下面要实现的这个CodeWizard非常简单。其功能主要是将一个数据表映射成一个类。这个类提供了Add和Save方法以及和数据表的每个字段相对应的属性。使用这个类可以向数据表添加记录。为了便于描述,将这个数据表保存成xml文件格式。每条记录为一个item结点,每一个字段为这个结点的一个属性。表名为这个xml文件的根结点名称。这个xml文件的格式如下所示:
<item id = "01 " name = "Bill "/>
<item id = "02" name = "Mike" />
</MyTable>
这个CodeWizard通过一个模板文件来定义数据表的结构。模板文件的格式如下:
<id type = "System.Int32"/>
<name type = "System.String"/>
</MyTable>
其中type为字段的类型,它的值是在.net framework中的System中定义的简单类型,如System.Int32、System.String、System.Char等。下面就详细讨论如何利用这个模板文件和CodeDOM技术来生成C#和VB.net的源代码。
三、CodeDOM的结构
CodeDOM由两部分组成:
1. 用于描述抽象代码结构的一组类。其中CodeCompileUnit类是这些类的根。代表一个源码文件(如C#的*.cs和VB.net的*.vb)。在使用CodeDOM时,必须先建立一个CodeCompileUnit类的对象,然后在这个对象中加入必要的namespace、class等面向对象元素。
用于生成和编译源代码的类。这个类必须从CodeDomProvider类继承。每种.net framework
所支持的语言都有自己的CodeDomProvider类。如在C#中的CodeDomProvider类叫CSharpCodeProvider,而在VB.net中叫VBCodeProvider。
四、数据表类的定义
要用CodeDOM定义一个类需要三步:
1. 建立一个CodeCompileUnit对象。这个类相当于一个源码文件。
2. 建立一个CodeNamespace对象。理论上在.net framework上运行的程序语言,如C#、VB.net
等,可以没有namespace。但在CodeDOM中必须使用这个类,如果不想要namespace,可以将namespace的名字设为 null或空串。
建立一个CodeTypeDeclaration对象。这个类可以建立Class和Interface两种Type。在
这个例子中只建立Class。如果想建立Interface,只需将IsInterface属性设为true即可。
主要的实现代码如下:
private CodeNamespace m_CodeNameSpace;
private CodeTypeDeclaration m_Class;
private void InitCodeDom()
{
m_CodeCompileUnit = new CodeCompileUnit();
m_CodeNameSpace = new CodeNamespace("xml.tables");
m_CodeCompileUnit.Namespaces.Add(m_CodeNameSpace);
m_Class = new CodeTypeDeclaration(m_ClassName);
m_CodeNameSpace.Types.Add(m_Class);
}
其中namespace的名子是“xml.tables”。在建立完namespace后,将其加入到m_CodeCompileUnit的Namespaces集合中。m_ClassName是一个String变量,它的值就是数据表的表名。最后将所建立的类加入到namespace的Types集合中。在产生完类后。需要在这个类中加入四部分内容,它们分别是:全局变量、属性、构造函数和方法(Add和Save方法)。下面就分别讨论它们的实现过程。
五、全局变量的生成
这个数据表类中有四种全局变量:用于操作xml文件的类型为XmlDocument的变量、用于保存数据表文件名的变量、用于确定是否为加入状态的Boolean型变量、以及用于保存每个字段值的变量组。具体实现代码如下:
{
// 产生 "private XmlDocument m_xml = new XmlDocument();"
CodeMemberField xml = new CodeMemberField("System.Xml.XmlDocument", "m_xml");
CodeObjectCreateExpression createxml = new CodeObjectCreateExpression("System.Xml.XmlDocument");
xml.InitExpression = createxml;
m_Class.Members.Add(xml);
// 产生 "private String m_XmlFile;"
CodeMemberField xmlfile = new CodeMemberField("System.String", "m_XmlFile");
m_Class.Members.Add(xmlfile);
// 根据模板文件产生保存字段值的变量
String fieldname = "", fieldtype = "";
foreach (XmlNode xn in m_Xml.DocumentElement.ChildNodes)
{
fieldname = "m_" + xn.Name;
fieldtype = xn.Attributes["type"].Value;
CodeMemberField field = new CodeMemberField(fieldtype, fieldname);
m_Class.Members.Add(field);
}
// 产生 "private bool m_AddFlag;"
CodeMemberField addflag = new CodeMemberField("System.Boolean", "m_AddFlag");
m_Class.Members.Add(addflag);
}
在以上代码中每段程序上方的注释是它们所生成的C#源代码。在输入这段代码之前,需要引入两个namespace。
using System.CodeDom.Compiler;
五、属性的生成
在数据表类中每个属性代表数据表的一个字段,名子就是字段名。这些属性和保存字段的全局变量一一对应。下面是具体的实现代码:
{
String fieldname = "", fieldtype = "";
foreach (XmlNode xn in m_Xml.DocumentElement.ChildNodes)
{
fieldname = xn.Name;
fieldtype = xn.Attributes["type"].Value;
CodeMemberProperty property = new CodeMemberProperty();
property.Attributes = MemberAttributes.Public | MemberAttributes.Final;
property.Name = fieldname;
property.Type = new CodeTypeReference(fieldtype);
property.HasGet = true;
property.HasSet = true;
CodeVariableReferenceExpression field = new CodeVariableReferenceExpression("m_" + fieldname);
// 产生 return m_property
CodeMethodReturnStatement propertyReturn = new CodeMethodReturnStatement(field);
property.GetStatements.Add(propertyReturn);
// 产生 m_property = value;
CodeAssignStatement propertyAssignment = new CodeAssignStatement(field,
new CodePropertySetValueReferenceExpression());
property.SetStatements.Add(propertyAssignment);
m_Class.Members.Add(property);
}
}
这些生成的属性是可读写的。这就需要将HasGet和HasSet两个属性设为true,然后分别将get和set方法中的语句分别加到GetStatements和SetStatements中。
RSS订阅






收 藏
推 荐