- 浏览: 646534 次
- 性别:
- 来自: 广州
文章分类
最新评论
CodeDOM是.net framework的一项重要的源代码生成技术。本文详细讨论了CodeDOM的原理以及如何利用CodeDOM技术实现一个与语言无关的Code Wizard。并给出了一个用C#语言实现的例子。
一、什么是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生成和编译源代码的过程。
图1CodeDom生成和编译源代码的过程
从上图可以看出,CodeWizard只使用CodeDOM对语言进行抽象,然后通过CodeDomProvider生成源代码。最后通过编译器生成中间语言。下面将详细讨论如何利用CodeDOM来实现CodeWizard。
二、实现CodeWizard
下面要实现的这个 CodeWizard非常简单。其功能主要是将一个数据表映射成一个类。这个类提供了Add和Save方法以及和数据表的每个字段相对应的属性。使用这个 类可以向数据表添加记录。为了便于描述,将这个数据表保存成xml文件格式。每条记录为一个item结点,每一个字段为这个结点的一个属性。表名为这个 xml文件的根结点名称。这个xml文件的格式如下所示:
<itemid="01"name="Bill"/>
<itemid="02"name="Mike"/>
</MyTable>
这个CodeWizard通过一个模板文件来定义数据表的结构。模板文件的格式如下:
<idtype="System.Int32"/>
<nametype="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即可。
主要的实现代码如下:
privateCodeNamespacem_CodeNameSpace;
privateCodeTypeDeclarationm_Class;
privatevoidInitCodeDom()
{
m_CodeCompileUnit=newCodeCompileUnit();
m_CodeNameSpace=newCodeNamespace("xml.tables");
m_CodeCompileUnit.Namespaces.Add(m_CodeNameSpace);
m_Class=newCodeTypeDeclaration(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型变量、以及用于保存每个字段值的变量组。具体实现代码如下:
{
//产生"privateXmlDocumentm_xml=newXmlDocument();"
CodeMemberFieldxml=newCodeMemberField("System.Xml.XmlDocument","m_xml");
CodeObjectCreateExpressioncreatexml=newCodeObjectCreateExpression("System.Xml.XmlDocument");
xml.InitExpression=createxml;
m_Class.Members.Add(xml);
//产生"privateStringm_XmlFile;"
CodeMemberFieldxmlfile=newCodeMemberField("System.String","m_XmlFile");
m_Class.Members.Add(xmlfile);
//根据模板文件产生保存字段值的变量
Stringfieldname="",fieldtype="";
foreach(XmlNodexninm_Xml.DocumentElement.ChildNodes)
{
fieldname="m_"+xn.Name;
fieldtype=xn.Attributes["type"].Value;
CodeMemberFieldfield=newCodeMemberField(fieldtype,fieldname);
m_Class.Members.Add(field);
}
//产生"privateboolm_AddFlag;"
CodeMemberFieldaddflag=newCodeMemberField("System.Boolean","m_AddFlag");
m_Class.Members.Add(addflag);
}
在以上代码中每段程序上方的注释是它们所生成的C#源代码。在输入这段代码之前,需要引入两个namespace。
usingSystem.CodeDom.Compiler;
五、属性的生成
在数据表类中每个属性代表数据表的一个字段,名子就是字段名。这些属性和保存字段的全局变量一一对应。下面是具体的实现代码:
{
Stringfieldname="",fieldtype="";
foreach(XmlNodexninm_Xml.DocumentElement.ChildNodes)
{
fieldname=xn.Name;
fieldtype=xn.Attributes["type"].Value;
CodeMemberPropertyproperty=newCodeMemberProperty();
property.Attributes=MemberAttributes.Public|MemberAttributes.Final;
property.Name=fieldname;
property.Type=newCodeTypeReference(fieldtype);
property.HasGet=true;
property.HasSet=true;
CodeVariableReferenceExpressionfield=newCodeVariableReferenceExpression("m_"+fieldname);
//产生returnm_property
CodeMethodReturnStatementpropertyReturn=newCodeMethodReturnStatement(field);
property.GetStatements.Add(propertyReturn);
//产生m_property=value;
CodeAssignStatementpropertyAssignment=newCodeAssignStatement(field,
newCodePropertySetValueReferenceExpression());
property.SetStatements.Add(propertyAssignment);
m_Class.Members.Add(property);
}
}
这些生成的属性是可读写的。这就需要将HasGet和HasSet两个属性设为true,然后分别将get和set方法中的语句分别加到GetStatements和SetStatements中。
六、构造函数的生成
构造函数的主要工作是打开数据表。如果数据表不存在,就创建这个数据表文件。在编写代码之前,需要先定义三个全局变量。因为这三个全局变量在程序中会多次用到。它们的类型都是CodeVariableReferenceExpression。这个类型变量其实在生成源码中的作用就是对某一个变量的引用。具体的实现代码如下:
privateCodeVariableReferenceExpressionm_XmlExpression;
privateCodeVariableReferenceExpressionm_AddFlagExpression;
privatevoidInitVariants()
{
m_XmlFileExpression=newCodeVariableReferenceExpression("m_XmlFile");
m_XmlExpression=newCodeVariableReferenceExpression("m_xml");
m_AddFlagExpression=newCodeVariableReferenceExpression("m_AddFlag");
}
下面是生成构造函数的源代码:
{
//定义构造函数
CodeConstructorconstructor=newCodeConstructor();
constructor.Parameters.Add(newCodeParameterDeclarationExpression("System.String","xmlFile"));
constructor.Attributes=MemberAttributes.Public;
//产生"m_XmlFile=xmlFile;"
CodeAssignStatementassignXmlFile=newCodeAssignStatement(m_XmlFileExpression,
newCodeVariableReferenceExpression("xmlFile"));
//产生"m_xml.LoadXml("…");"
CodeMethodInvokeExpressioninvokeLoadXml=newCodeMethodInvokeExpression(m_XmlExpression,"LoadXml",
newCodePrimitiveExpression("<?xmlversion=\"1.0\"encoding=\"gb2312\"?><"+m_Xml.DocumentElement.Name
+"></"+m_Xml.DocumentElement.Name+">"));
//产生"m_xml.Save(m_XmlFile);"
CodeMethodInvokeExpressioninvokeSave=newCodeMethodInvokeExpression(m_XmlExpression,"Save",
m_XmlFileExpression);
CodeStatementCollectionstatements=newCodeStatementCollection();
statements.Add(invokeLoadXml);
statements.Add(invokeSave);
//产生if语句:"if(System.IO.File.Exists(m_XmlFile))else"
CodeConditionStatementifStatement=newCodeConditionStatement(newCodeMethodInvokeExpression(
newCodeVariableReferenceExpression("System.IO.File"),"Exists",m_XmlFileExpression),newCodeStatement[]{},
newCodeStatement[]{statements[0],statements[1]});
//产生"m_xml.Load(m_XmlFile);"
CodeMethodInvokeExpressioninvokeLoad=newCodeMethodInvokeExpression(m_XmlExpression,"Load",
m_XmlFileExpression);
//产生"m_AddFlag=false;"
CodeAssignStatementassignAddFalse=newCodeAssignStatement(m_AddFlagExpression,
newCodePrimitiveExpression(false));
constructor.Statements.Add(assignXmlFile);
constructor.Statements.Add(ifStatement);
constructor.Statements.Add(invokeLoad);
constructor.Statements.Add(assignAddFalse);
m_Class.Members.Add(constructor);
}
七、Add和Save方法生成
Add方法只有一条语句,功能是将m_AddFlag设为true,以使数据表类处于加入状态。Save方法比较复杂。它的功能是当m_AddFlag为true时在数据表文件的最后加入一条记录,并保存。具体实现代码如下:
{
CodeTypeReferencevoidReference=newCodeTypeReference("System.void");
//产生Add方法
CodeMemberMethodadd=newCodeMemberMethod();
add.ReturnType=voidReference;
add.Name="add";
add.Attributes=MemberAttributes.Public|MemberAttributes.Final;
CodeAssignStatementassignAddTrue=newCodeAssignStatement(m_AddFlagExpression,
newCodePrimitiveExpression(true));
add.Statements.Add(assignAddTrue);
m_Class.Members.Add(add);
//产生Save方法
CodeMemberMethodsave=newCodeMemberMethod();
save.ReturnType=voidReference;
save.Name="save";
save.Attributes=MemberAttributes.Public|MemberAttributes.Final;
System.Collections.Generic.List<CodeStatement>ifStatements=
newSystem.Collections.Generic.List<CodeStatement>();
//产生"XmlNodexn=m_xml.CreateNode(XmlNodeType.Element,"item","");"
CodeVariableDeclarationStatementxmlNode=newCodeVariableDeclarationStatement("System.Xml.XmlNode","xn");
CodeMethodInvokeExpressioncreateNode=newCodeMethodInvokeExpression(m_XmlExpression,"CreateNode",
newCodeExpression[]{newCodeVariableReferenceExpression("System.Xml.XmlNodeType.Element"),
newCodePrimitiveExpression("item"),
newCodePrimitiveExpression("")});
xmlNode.InitExpression=createNode;
ifStatements.Add(xmlNode);
//产生"XmlAttributexa=null;"
CodeVariableDeclarationStatementxmlAttr=newCodeVariableDeclarationStatement("System.Xml.XmlAttribute","xa");
xmlAttr.InitExpression=newCodePrimitiveExpression(null);
ifStatements.Add(xmlAttr);
//产生字段属性
CodeStatementCollectionstatements=newCodeStatementCollection();
foreach(XmlNodexninm_Xml.DocumentElement.ChildNodes)
{
CodeMethodInvokeExpressioncreateAttribute=newCodeMethodInvokeExpression(m_XmlExpression,
"CreateAttribute",newCodePrimitiveExpression(xn.Name));
CodeAssignStatementassignxa=newCodeAssignStatement(
newCodeVariableReferenceExpression("xa"),createAttribute);
CodeMethodInvokeExpressioninvokeToString=newCodeMethodInvokeExpression(
newCodeVariableReferenceExpression("m_"+xn.Name), "ToString");
CodeAssignStatementassignValue=newCodeAssignStatement(
newCodeVariableReferenceExpression("xa.Value"),invokeToString);
CodeMethodInvokeExpressioninvokeAppend=newCodeMethodInvokeExpression(
newCodeVariableReferenceExpression("xn.Attributes"),
"Append",new CodeVariableReferenceExpression("xa"));
statements.Add(invokeAppend);
ifStatements.Add(assignxa);
ifStatements.Add(assignValue);
ifStatements.Add(statements[0]);
}
//产生"m_xml.DocumentElement.AppendChild(xn);"
CodeMethodInvokeExpressioninvokeAppendChild=newCodeMethodInvokeExpression(new
CodeVariableReferenceExpression("m_xml.DocumentElement"),"AppendChild",
newCodeVariableReferenceExpression("xn"));
statements.Clear();
statements.Add(invokeAppendChild);
ifStatements.Add(statements[0]);
//产生"m_xml.Save(m_XmlFile);"
CodeMethodInvokeExpressioninvokeSave=newCodeMethodInvokeExpression(m_XmlExpression,
"Save",m_XmlFileExpression);
statements.Clear();
statements.Add(invokeSave);
ifStatements.Add(statements[0]);
//产生"m_AddFlag=false;"
CodeAssignStatementassignAddFalse=newCodeAssignStatement(m_AddFlagExpression,
newCodePrimitiveExpression(false));
ifStatements.Add(assignAddFalse);
//产生if语句:"if(m_AddFlag)"
CodeConditionStatementifStatement=newCodeConditionStatement(m_AddFlagExpression,
ifStatements.ToArray());
save.Statements.Add(ifStatement);
m_Class.Members.Add(save);
}
八、生成源代码
生成具体语言的源代码需要一个从CodeDomProvider继承的类。对于C#而言是CSharpCodeProvider类。实现代码如下:
publicvoidSaveCSharp(Stringfilename)
{
IndentedTextWritertw=newIndentedTextWriter(newStreamWriter(filename,false),"");
CodeDomProviderprovide=newCSharpCodeProvider();
provide.GenerateCodeFromCompileUnit(m_CodeCompileUnit,tw,newCodeGeneratorOptions());
tw.Close();
}
在使用CSharpCodeProvider类时需要用到m_CodeCompileUnit这个全局变量。这样可产生一个*.cs文件。以上代码中的IndentedTextWriter类是建立一个文件的Writer,用于向这个文件中输出源代码。但和其它的Writer不同的是它的输出是缩进的(以四个空格进行缩进)。如是想生成VB.net的代码,只需将CSharpCodeProvider改为VBCodeProvider即可。
九、编译源代码
到 现在为止,这个数据表类的源代码已经全部生成了。你可以将这个源文件直接加入到自己的工程中。或者直接将其编译成*.dll文件,然后在程序中调用。如果 想编译,可以直接调用指定语言的编译器(如C#中的csc.exe)。但这样不是太方便。在CodeDOM中提供了一种机制,可以在程序中通过 CodeDomProvider直接调用指定语言的编译器。下面是编译C#源程序的一个例子。
{
CompilerParameterscp=newCompilerParameters(newString[]{"System.Xml.dll"},targetFile,false);
CodeDomProviderprovider=newCSharpCodeProvider();
cp.GenerateExecutable=false;
//调用编译器
CompilerResultscr=provider.CompileAssemblyFromFile(cp,sourcefile);
if(cr.Errors.Count>0)
{
//显示编译错误
foreach(CompilerErrorceincr.Errors)
System.Windows.Forms.MessageBox.Show(ce.ToString());
}
}
对于以上代码有两点说明:
- 使用CodeDomProvider调用编译器时也需要传递相应的参数,如在本例中将System.Xml.dll
作为一个参数,表示目标文件需要调用这个dll中的资源。
- 在调用编译器后,如果出现错误,可使用cr.Errors获得错误信息。
十、结束语
我花了一个晚上的时间实现了这个简单的例子,并用C#2.0调试通过,只是为了抛砖引玉。自动生成源代码有很多的方法,但使用CodeDom生成源代码会有更大的灵活性,主要表现在以下三个方面:
1. 语言无关。即只要是.net framework所支持的语言,并且这种语言提供了CodeDomProvider。
就可以生成这种语言的源代码。
2. 如果所生成的语言是测试版或要将这种语言升级到下一个版本,也可以考虑使用CodeDOM。
因为当这种语言的语法有所变化时,CodeDomProvider也会随之升级。因此,使用CodeDOM的Code Wizards也会随着CodeDOM而升级,这样就不必修改Code Wizards的源代码了。
3. 如果所生成的一种语言是你所不熟悉的,如果不使用CodeDOM,必须要熟悉这种语言的语
法,才能生成它的源代码。而使用CodeDOM却可以避免这一点。因为CodeDOM是使用抽象的object graph来描述语言的。而语言的具体语法是由CodeDomProvider所决定的。
其实CodeDOM不仅可以用在Code Wizards上,也可以用在许多其它地方,如可以生成Web
Services的客户端代理(Client Proxies),或根据UML图生成类的构架代码。总之,使用CodeDom可以大大降低和语言的偶合度,并且很容易维护和升级系统。
相关推荐
CodeDOM(Code Document Object Model)是.NET框架的一部分,它允许开发者动态地生成源代码,而反射则是C#中强大的特性,可以用于运行时检查和操作类型。下面将详细解释这两个知识点以及它们在实际应用中的示例。 ...
CodeDom,全称为Code Document Object Model,是.NET框架中用于生成和编译源代码的抽象语法树(AST)表示。这个技术主要应用于自动化代码生成、动态类型创建以及元编程等场景。在C#中,CodeDom提供了一组类库,如`...
.NET 提供了一个System.CodeDom的命名空间,它允许用户动态编译和创建程序集。本文提供了有关如何动态创建程序集的内容。 用途 1、模板化代码生成:生成适用于 ASP.NET、XML Web 服务客户端代理、代码向导、设计器或...
由于原版损坏,故我修复后继续上传~希望大家共享,保留原分值不变~ ...也就是说,你可以使用该模型“发明”一个自己的.net语言,用你的语言编写程序,再翻译成codeDom,最后编译成可以执行的.net应用程序。
在.NET环境中,CodeDom扮演着一个中间角色,它允许程序员以对象模型的方式描述源代码,然后可以将这些描述转换成实际的C#、VB.NET或其他.NET支持的语言的源代码。 CodeDom主要用于以下场景: 1. **动态代码生成**:...
CodeDom //Console.WriteLine("Test0: {0}", Evaluator.EvaluateToInteger("(30 + 4) * 2")); //Console.WriteLine("Test1: {0}", Evaluator.EvaluateToString(@"""Hello "" + ""There""")); //Console....
3. **多语言支持**:CodeDom 支持多种 .Net 语言,包括 VB.NET、C# 和 J# 等,这为开发者提供了极大的灵活性。 4. **扩展性**:通过自定义 CodeProvider,可以轻松地扩展 CodeDom 的功能,支持更多的编程语言或定制...
例如,要使用CodeDom生成一个简单的C#类,你可以首先创建一个`CodeCompileUnit`对象,然后添加一个`CodeNamespace`,接着在命名空间中添加`CodeTypeDeclaration`表示类,最后在类中添加`CodeMemberMethod`表示方法。...
这使得C#可以轻松地与IronPython、IronRuby等动态语言交互,或者实现动态对象和方法。 三、Expression Trees 表达式树(Expression Trees)是另一种动态编程工具,它允许开发者将代码表示为数据结构,通常用于 ...
本示例中的“C# 实现 动态生成.EXE 程序和源码”是一个关于利用C#编程语言动态编译和生成可执行文件(.EXE)的过程,这涉及到对.NET编译器工作原理的理解和运用。 首先,我们需要了解.NET编译器的工作流程。C#源...
在.NET框架中,C#程序员有时需要在运行时动态地执行...这就是使用`C#`和`CodeDom`实现动态执行字符串的基本流程。在实际项目中,你可以根据需要扩展这个功能,比如处理更复杂的代码逻辑、捕获执行结果、参数传递等。
通过`System.CodeDom.Compiler`和`Microsoft.CSharp`命名空间,开发者可以不依赖IDE,直接在代码中实现C#代码的编译和运行,这对于实现高度动态的应用程序或者提高代码的灵活性非常有用。 综上所述,`C#的Compiler...
《C#技术内幕》是微软Visual Studio .NET程序开发系列丛书中的一本,专注于深入解析C#编程语言的各个方面。这本书对于想要深入了解C#、掌握其核心概念和技术的开发者来说,是一份宝贵的资源。以下是对C#技术的一些...
C#语言中的命名空间是组织代码的一个重要机制,它允许开发者将相关的类、接口和枚举等编程元素分组在一起,提高代码的可读性和可维护性。在C#中,命名空间通常用来避免命名冲突,尤其是在大型项目或库中。 `...
在C#语言中,API扮演着至关重要的角色,它允许开发者访问操作系统、库、框架以及硬件设备的功能。本篇将深入探讨C# API的使用,包括其基本概念、主要类别、使用方法以及如何通过API来增强C#应用的功能。 首先,让...
C# ReoScript脚本引擎是一个强大的工具,用于在C#环境中执行和集成脚本代码。这个Demo包提供了源代码,使得开发者可以深入理解其工作原理,并根据需求进行定制和扩展。ReoScript引擎旨在提高开发效率,允许快速实现...
C#中的动态编译主要通过System.CodeDom和Roslyn两个API来实现。 1. **System.CodeDom**: 这是.NET框架的一部分,提供了一组类用于生成源代码。CodeDOM(Code Document Object Model)模型可以用来创建源代码的抽象...
Nyan-Compiler是一款基于C#和VB.NET编程语言的简单集成开发环境(IDE),利用了.NET框架中的CodeDOM(代码对象模型)技术。CodeDOM是一个强大的工具,允许程序员在运行时生成源代码,并能以多种.NET语言进行编译。这...
在IT行业中,C#是一种广泛使用的面向对象的编程语言,尤其在Windows应用程序、游戏开发以及Web服务中。C#动态编译是C#语言的一个重要特性,它允许开发者在程序运行时编译代码,提供了极大的灵活性和便利性。这篇内容...