序言:前几天整理资料时发现以前翻译的一篇关于CodeDom的文章,虽然题材比较老了,但还是可能对部分兄弟有用,贴出来与大家共享,不妥之处敬请指出。原文来自CSharpCorner:http://www.c-sharpcorner.com/UploadFile/mgold/CodeDomCalculator08082005003253AM/CodeDomCalculator.aspx
介绍:
借助CodeDom和Reflection我们可以动态编译C#代码并使之在程序中任何地方都能运行。这个强大的特性允许我们创建在Windows窗体甚至C#代码行中都可以运算数学表达式的CodeDom计算器。首先我们需要借助System.Math类来进行计算,但我们并不需要在算式前加上Math. 前缀,下面将向您展现CodeDom计算器是如何实现的。

图 1 – 运行中的CodeDom计算器
用法:
CodeDom可依下列两种方法使用:
1、用C#语法输入你要计算的数学表达式
2、写一个计算复杂算式的C#代码块
第一种方法仅需要按图2中的方法输入算式即可。

图 2 – 在CodeDom计算器中运算一个较长的算式
在第二种方法中我们实现的手段略有差异。最上面一行写一个answer加上分号,之后你就可以写任何C#代码,在代码片断的末端将你需要得到的答案赋予answer变量。在写代码时同上也不用加Math类前缀,图3是用CodeDom求从1到10的和的示例:

图 3 – 用CodeDom计算从1到10的和
创建并运行Calculator类
运算表达式的下面几步是:
1、使用CodeDom依据算式创建C#代码
2、使用CodeDom编译器将这段代码编译为程序集
3、创建一个Calculator类的实例
4、调用Calculator类中的Calculate方法得到答案
下表就是我们要创建的CodeDom类,Calculate方法将包含我们输入CodeDom计算器的数学表达式。

图 4 – UML反工得到的Calculator类
事实上图3中用到的CodeDom程序集是由下列代码创建的。下一节我们将讲述更多关于如何创建包含CodeDom所有方法的类,这些方法真是酷毙了。真如您所见,我们的表达式被传入Calculate方法中。我们需要将answer;放到第一行是为了在Calculate方法强制置入一个哑元行来引入较大的代码块(这个哑元行是Answer = answer;)。如果我们输入一个简单的表达式如1+1,在代码内将产生一行Answer = 1 + 1;
列表 1 – CodeDom为计算器产生的代码
|
namespace ExpressionEvaluator {
using System; using System.Windows.Forms;
public class Calculator { private double answer;
/// Default Constructor for class public Calculator() { //TODO: implement default constructor }
// The Answer property is the returned result public virtual double Answer { get { return this.answer; }
set { this.answer = value; } }
/// Calculate an expression public virtual double Calculate() { Answer = answer; for (int i = 1; i <= 10; i++) answer = answer + i;
return this.Answer; }
} }
|
代码分析
点击计算按钮后, 代码产生、编译、运行。 列表2 展示了按顺序执行这几步的calculate event handler。尽管这不是全部的代码,所有的步骤已经在BuildClass, CompileAssembly和RunCode方法里全部包括:.
列表 2 – 计算数学表达式的Event Handler
|
private void btnCalculate_Click(object sender, System.EventArgs e) { // Blank out result fields and compile result fields InitializeFields();
// change evaluation string to pick up Math class members string expression = RefineEvaluationString(txtCalculate.Text);
// build the class using codedom BuildClass(expression);
// compile the class into an in-memory assembly. // if it doesn't compile, show errors in the window CompilerResults results = CompileAssembly();
// write out the source code for debugging purposes Console.WriteLine("...........................\r\n"); Console.WriteLine(_source.ToString());
// if the code compiled okay, // run the code using the new assembly (which is inside the results) if (results != null && results.CompiledAssembly != null) { // run the evaluation function RunCode(results); } }
|
CodeDom看起来怎么样呢? 如果你仔细观察过CodeDom中的类, 就会发现它们几乎就是违反语法的。每个构造器使用其他CodeDom对象来构造自己并构造其他语法片断的合成物。表1展示了我们在这个工程中构造程序集用到的所有类和他们各自的用途。
|
CodeDom 对象
|
用途
|
|
CSharpCodeProvider
|
生成C#代码的Provider
|
|
CodeNamespace
|
构造名称空间的类
|
|
CodeNamespaceImport
|
创建调用申明
|
|
CodeTypeDeclaration
|
创建类结构
|
|
CodeConstructor
|
创建构造器
|
|
CodeTypeReference
|
创建一个类型的引用
|
|
CodeCommentStatement
|
创建C#注释
|
|
CodeAssignStatement
|
创建委派申明
|
|
CodeFieldReferenceExpression
|
创建一个field引用
|
|
CodeThisReferenceExpression
|
创建一个this指针
|
|
CodeSnippetExpression
|
创建一个在代码中指定的文字字符串 (用于放置表达式)
|
|
CodeMemberMethod
|
创建一个新的方法
|
表 1 – 构建计算器需要用到的CodeDom类
让我们看看列表3中用来生成代码的CodeDom方法。大家可以看到用CodeDom生成代码是比较容易的,因为它把复杂的代码生成工作分割成了几个简单的部分。我们先创建一个生成器,在本例中我们要生成C#代码所以创建了C#生成器;然后开始创建并装配各个部分。首先创建命名空间,然后添加导入我们需要的各个类库,其次创建类,给类添加一个构造器一个属性和一个方法。在这个方法里,我们添加了方法的声明,声明中连入文本框输入的要求值的运算表达式,在CodeSnippetExpression构造器中使用输入的运算表达式这样我们就能从赋值字符串中直接生成代码。这个表达式也使用了CodeAssignStatement构造器,这样我们就能将其分配给Answer属性。当我们完成装配CodeDom各个层次的组成部分后,只需要用已装配命名空间的CodeDom构造器来调用GenerateCodeFromNamespace即可。由它输出字符串流到StringWriter并内部指派一个可以直接从字符串中释放全部代码集合的StringBuilder。
列表 3 – 使用CodeDom类构造Calculator类
|
/// <summary>
/// Main driving routine for building a class
/// </summary> void BuildClass(string expression) { // need a string to put the code into _source = new StringBuilder();
StringWriter sw = new StringWriter(_source);
//Declare your provider and generator CSharpCodeProvider codeProvider = new CSharpCodeProvider(); ICodeGenerator generator = codeProvider.CreateGenerator(sw); CodeGeneratorOptions codeOpts = new CodeGeneratorOptions(); CodeNamespace myNamespace = new CodeNamespace("ExpressionEvaluator");
myNamespace.Imports.Add(new CodeNamespaceImport("System")); myNamespace.Imports.Add(new CodeNamespaceImport("System.Windows.Forms"));
//Build the class declaration and member variables CodeTypeDeclaration classDeclaration = new CodeTypeDeclaration();
classDeclaration.IsClass = true; classDeclaration.Name = "Calculator"; classDeclaration.Attributes = MemberAttributes.Public; classDeclaration.Members.Add(FieldVariable("answer", typeof(double), MemberAttributes.Private));
//default constructor CodeConstructor defaultConstructor = new CodeConstructor(); defaultConstructor.Attributes = MemberAttributes.Public; defaultConstructor.Comments.Add(new CodeCommentStatement("Default Constructor for class", true)); defaultConstructor.Statements.Add(new CodeSnippetStatement("//TODO: implement default constructor")); classDeclaration.Members.Add(defaultConstructor);
//home brewed method that uses CodeDom to make a property classDeclaration.Members.Add(this.MakeProperty("Answer", "answer", typeof(double)));
//Our Calculate Method CodeMemberMethod myMethod = new CodeMemberMethod();
myMethod.Name = "Calculate"; myMethod.ReturnType = new CodeTypeReference(typeof(double)); myMethod.Comments.Add(new CodeCommentStatement("Calculate an expression", true)); myMethod.Attributes = MemberAttributes.Public; myMethod.Statements.Add(new CodeAssignStatement(new CodeSnippetExpression("Answer"), new CodeSnippetExpression(expression)));
// Include the generation below if you want your answer to pop up in a message box // myMethod.Statements.Add(new CodeSnippetExpression("MessageBox.Show(String.Format(\"Answer = {0}\", Answer))"));
// return answer myMethod.Statements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), "Answer")));
classDeclaration.Members.Add(myMethod);
//write code myNamespace.Types.Add(classDeclaration); generator.GenerateCodeFromNamespace(myNamespace, sw, codeOpts); // cleanup sw.Flush(); sw.Close(); }
|
编译
编译被分解为3个部分: 创建CodeDom编译器,创建编译参数,并如列表4所示将代码编译进程序集。
列表 4 - 使用CodeDom编译程序集
|
/// <summary> /// Compiles the c# into an assembly if there are no syntax errors /// </summary> /// <returns></returns>
private CompilerResults CompileAssembly() { // create a compiler ICodeCompiler compiler = CreateCompiler(); // get all the compiler parameters CompilerParameters parms = CreateCompilerParameters(); // compile the code into an assembly CompilerResults results = CompileCode(compiler, parms, _source.ToString());
return results; }
|
CreateCompiler代码创建C# CodeDom provider对象并从其中创建一个编译器对象。
列表 5 – 创建C#编译器对象
|
ICodeCompiler CreateCompiler() { //Create an instance of the C# compiler CodeDomProvider codeProvider = null; codeProvider = new CSharpCodeProvider(); ICodeCompiler compiler = codeProvider.CreateCompiler(); return compiler; }
|
如列表6所示,我们需要将编译器参数放到一起。我们还需要设定适当的编译器选项在内存中生成dll类库,我们也可以使用这些参数来添加各种引用库到包含System.Math类的系统库中。
列表 6 – 为编译器创建参数
|
/// <summary>
/// Creawte parameters for compiling
/// </summary>
/// <returns></returns>
CompilerParameters CreateCompilerParameters() { //add compiler parameters and assembly references CompilerParameters compilerParams = new CompilerParameters(); compilerParams.CompilerOptions = "/target:library /optimize"; compilerParams.GenerateExecutable = false; compilerParams.GenerateInMemory = true; compilerParams.IncludeDebugInformation = false; compilerParams.ReferencedAssemblies.Add("mscorlib.dll"); compilerParams.ReferencedAssemblies.Add("System.dll"); compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");
return compilerParams; }
|
最终我们需要编译的是代码。这是用CompileAssemblyFromSource方法完成的,如列表7所示。这个方法提取列表5设定的参数与字符串形式的代码集合并将代码编译为一个程序集。对该程序集的引用将指派给编译器结果。如果编译过程中有错误,我们在地步的文本框中输出并设定编译器结果为null,这样就不用再去尝试编译并运行程序集了。
列表 7 – 使用编译器参数编译创建的代码生成程序集
|
private CompilerResults CompileCode(ICodeCompiler compiler, CompilerParameters parms, string source) { //actually compile the code
CompilerResults results = compiler.CompileAssemblyFromSource( parms, source);
//Do we have any compiler errors? if (results.Errors.Count > 0) { foreach ( |