代码的动态编译并执行是一个.NET平台提供给我们的很强大的工具用以灵活扩展(当然是面对内部开发人员)复杂而无法估算的逻辑,并通过一些额外的代码来扩展我们已有 的应用程序。这在很大程度上给我们提供了另外一种扩展的方式(当然这并不能算是严格意义上的扩展,但至少为我们提供了一种思路)。
动态代码执行可以应用在诸如模板生成,外加逻辑扩展等一些场合。一个简单的例子,为了网站那的响应速度,HTML静态页面往往是我们最好的选择,但基于数据驱动的网站往往又很难用静态页面实现,那么将动态页面生成html的工作或许就是一个很好的应用场合。另外,对于一些模板的套用,我们同样可以用它来做。另外这本身也是插件编写的方式。
最基本的动态编译
.Net为我们提供了很强大的支持来实现这一切我们可以去做的基础,主要应用的两个命名空间是:System.CodeDom.Compiler和Microsoft.CSharp或Microsoft.VisualBasic。另外还需要用到反射来动态执行你的代码。动态编译并执行代码的原理其实在于将提供的源代码交予CSharpCodeProvider来执行编译(其实和CSC没什么两样),如果没有任何编译错误,生成的IL代码会被编译成DLL存放于于内存并加载在某个应用程序域(默认为当前)内并通过反射的方式来调用其某个方法或者触发某个事件等。之所以说它是插件编写的一种方式也正是因为与此,我们可以通过预先定义好的借口来组织和扩展我们的程序并将其交还给主程序去触发。一个基本的动态编译并执行代码的步骤包括:
· 将要被编译和执行的代码读入并以字符串方式保存
· 声明CSharpCodeProvider对象实例
· 调用CSharpCodeProvider实例的CompileAssemblyFromSource方法编译
· 用反射生成被生成对象的实例(Assembly.CreateInstance)
· 调用其方法
以下代码片段包含了完整的编译和执行过程:
//get the code to compile
string strSourceCode = this.txtSource.Text;
// 1.Create a new CSharpCodePrivoder instance
CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();
// 2.Sets the runtime compiling parameters by crating a new CompilerParameters instance
CompilerParameters objCompilerParameters = new CompilerParameters();
objCompilerParameters.ReferencedAssemblies.Add("System.dll");
objCompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
objCompilerParameters.GenerateInMemory = true;
// 3.CompilerResults: Complile the code snippet by calling a method from the provider
CompilerResults cr = objCSharpCodePrivoder.CompileAssemblyFromSource(objCompilerParameters, strSourceCode);
if (cr.Errors.HasErrors)
{
string strErrorMsg = cr.Errors.Count.ToString() + " Errors:";
for (int x = 0; x < cr.Errors.Count; x++)
{
strErrorMsg = strErrorMsg + "\r\nLine: " +
cr.Errors[x].Line.ToString() + " - " +
cr.Errors[x].ErrorText;
}
this.txtResult.Text = strErrorMsg;
MessageBox.Show("There were build erros, please modify your code.", "Compiling Error");
return;
}
// 4. Invoke the method by using Reflection
Assembly objAssembly = cr.CompiledAssembly;
object objClass = objAssembly.CreateInstance("Dynamicly.HelloWorld");
if (objClass == null)
{
this.txtResult.Text = "Error: " + "Couldn''t load class.";
return;
}
object[] objCodeParms = new object[1];
objCodeParms[0] = "Allan.";
string strResult = (string)objClass.GetType().InvokeMember("GetTime", BindingFlags.InvokeMethod, null, objClass, objCodeParms);
this.txtResult.Text = strResult;
需要解释的是,这里我们在传递编译参数时设置了GenerateInMemory为true,这表明生成的DLL会被加载在内存中(随后被默认引用入当前应用程序域)。在调用GetTime方法时我们需要加入参数,传递object类型的数组并通过Reflection的InvokeMember来调用。在创建生成的Assembly中的对象实例时,需要注意用到的命名空间是你输入代码的真实命名空间。以下是我们输入的测试代码(为了方便,所有的代码都在外部输入,动态执行时不做调整):
namespace Dynamicly
{
public class HelloWorld
{
public string GetTime(string strName)
{
return "Welcome " + strName + ", Check in at " + System.DateTime.Now.ToString();
}
}
}
运行附件中提供的程序,可以很容易得到一下结果: