不能确定动态代码编译在什么地方是有意义的?一个普通情况就应该可以帮助阐明这个问题。假如你不得不从一个数据库中取出数据并将它放入另一个数据库。你应该只需使用一个SQL语句从源数据库中选取数据并插入目标数据库中,这只是小菜一碟,对不对?如果你正在拷贝生产数据以生成测试数据并需要改变数据以确保目标数据在以后开发中使用是安全的又将如何?你可能会构建一个数据传输系统(DTS)或某个其它传输机制,但是如果你这样做超过足够多的数据,这就会变成你每次为拷贝数据建立数据-擦除(data-scrubbing)机制而消耗时间。你可以写一个应用程序来加工并生成测试数据,但是每次你在一个不同的应用程序上用它时你都将不得不改动(应用程序)并创建新的算法。
走进动态代码编译。胜于不停地写一些一次性的代码,你可以创建一个有特定内部运作机制的应用程序来传送数据并在传送时运用代码段来改变数据。该代码段将代理每个你需要在数据上要做的动作。它们将被作为原始文本被储存在一个数据库中或某个它们可以很容易被修改的其它位置。代码段将被编译并在执行时同时应用到数据。这将允许你获得一个完全是不同的代码段的数据库,使得你可以很容易地恢复、修改并应用它而不用每次都要改变你的应用程序的根本。
这是个相当复杂的情况,但是它应该帮助你理解一些可能性。现在,让我们看看如何实现它。
CodeCompileUnit(代码编译单元) 为了动态编译一个类,从System.CodeDom命名空间的一个CodeCompileUnit开始。CodeCompileUnit包含一个程序图形。为了构建代码,你要创建一些支撑对象并将它们添加到CodeCompileUnit实例中去。这些对象:代表应该已经在你的代码中的,就像你准备在设计时要创建它的普通对象。
. CodeNamespace—代表指定的命名空间
. CodeTypeDeclaration—代表类型声明
. CodeMemberMethod—代表一个方法
一个HelloWorld例子 你可以使用下面的示例代码来生成包含一个接收单个参数并返回一个值的SayHello方法的代码。scriptBody方法的参数值成为SayHello方法的实体(body)。你将你的代码包含在接收影响结果的参数的一个static(静态)类中以创建CodeCompileUnit。
public static CodeCompileUnit CreateExecutionClass(string typeNamespace,
string typeName,
string scriptBody)
{
// 创建CodeCompileUnit以包含代码
CodeCompileUnit ccu = new CodeCompileUnit(); // 分配需要的命名空间
CodeNamespace cns = new CodeNamespace(typeNamespace);
cns.Imports.Add(new CodeNamespaceImport("System"));
ccu.Namespaces.Add(cns); // 创建新的类声明
CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);
cns.Types.Add(parentClass); // 创建获得一个参数并返回一个字符串的SayHello方法
CodeMemberMethod method = new CodeMemberMethod();
method.Name = "SayHello";
method.Attributes = MemberAttributes.Public;
CodeParameterDeclarationExpression arg = new CodeParameterDeclarationExpression(typeof(string), "inputMessage");
method.Parameters.Add(arg);
method.ReturnType = new CodeTypeReference(typeof(string)); // 添加方法实体需要的代码
CodeSnippetStatement methodBody =new CodeSnippetStatement(scriptBody);
method.Statements.Add(methodBody);
parentClass.Members.Add(method); return ccu;
}
CodeProvider(代码提供者)
现在你已经创建了一个CodeCompileUnit包含你的代码段,使用它来生成被编译到你的动态程序集中去的全部源代码。下面的静态方法首先从前面的例子中调用方法并且同时使用CSharpCodeProvider生成全部代码:
public static string GenerateCode(string typeNamespace,
string typeName,
string scriptBody)
{
// 调用我们前面的方法创建CodeCompileUnit
CodeCompileUnit ccu = CreateExecutionClass(typeNamespace,
typeName, scriptBody); CSharpCodeProvider provider = new CSharpCodeProvider();
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BlankLinesBetweenMembers = false;
options.IndentString = "\t"; StringWriter sw = new StringWriter();
try
{
provider.GenerateCodeFromCompileUnit(ccu, sw, options);
sw.Flush();
}
finally
{
sw.Close();
} return sw.GetStringBuilder().ToString();
}
作为一个例子,用输入值:"CodeGuru.DynamicCode","ScriptType",和"return inputMessage;"调用GenerateCode方法得出以下输出:
//---------------------------------------------------------------
// <auto-generated>
// 该代码是由工具生成的。
// 运行时版本:2.0.50630.0
// 更改这个文件可能导致不正确的(程序)动作并且如果代码被再次生成时将会丢掉这些更改。
// </auto-generated>
//---------------------------------------------------------------
namespace CodeGuru.DynamicCode {
using System;
public class ScriptType {
public virtual string SayHello(string inputMessage) {
return inputMessage;
}
}
}
在内存中编译 最后一步是获得生成的源代码并将它编译到一个当前的程序集中去。对于这个例子,你是将这个例子装入内存而不是一个物理文件。通过特定的编程语言提供者执行当前编译动作,在这个例程中就是CSharpCodeProvider。你设定任何预定的编译选项并从源代码编译这个程序集。
下面的示例代码从你已构建的代码中生成了一个程序集:
static Assembly CompileInMemory(string code)
{
CSharpCodeProvider provider = new CSharpCodeProvider(); CompilerParamet