代码编译


  代码编译是将外部的一段源程序进行实时编译,然后通过反射机制执行类的方法返回所需的结果。代码编译能有效提高程序的灵活度和可扩展性。使用 CodeCompiler 类可以将一段 C#/VB 代码、Code模型、或一组源文件编译成一个类或调用委托。

  Fireasy 提供了代码编译的支持,位于 Fireasy.Common.Compiler 命名空间下。


1、编译委托

  将一段代码编译成一个委托函数,使用委托函数传参调用得到结果。如下所示:

[TestMethod]
public void TestCompileDelegate()
{
    var code = @"
using System;
using System.Drawing;
public class CalculateClass
{
    // radian: 弧度
    // radius: 半径
    public static Point Calculate(double radian, int radius)
    {
        return new Point((int)(Math.Sin(radian) * radius), (int)(Math.Cos(radian) * radius));
    }
}";

    var compiler = new CodeCompiler();

    //添加外部程序集引用
    compiler.Assemblies.Add("system.drawing.dll");

    var func = compiler.CompileDelegate<Func<double, int, Point>>(code, "Calculate");

    var point = func(45, 100);

    Assert.AreEqual(85, poing.X);
    Assert.AreEqual(52, poing.Y);
}

  也可以省略 CompileDelegate 方法中的第二个参数 methodName,但前提是该代码中只定义唯一一个静态公开方法。


2、编译程序集

  将一段代码直接编译成一个完整的程序集。如下所示:

[TestMethod]
public void TestCompileAssembly()
{
    var code = @"
using System;
using System.Drawing;
public class CalculateClass
{
    // radian: 弧度
    // radius: 半径
    public static Point Calculate(double radian, int radius)
    {
        return new Point((int)(Math.Sin(radian) * radius), (int)(Math.Cos(radian) * radius));
    }

    public double CalculatePerimeter(int radius)
    {
        return 2 * Math.PI * radius;
    }
}

public class Foo
{
}";

    var compiler = new CodeCompiler();

    //添加外部程序集引用
    compiler.Assemblies.Add("system.drawing.dll");

    var assembly = compiler.CompileAssembly(code);

    Assert.AreEqual(2, assembly.GetExportedTypes().Length);
}

3、编译类型

  如果代码中只定义了一个类,可以使用 CompileType 方法编译并返回该类。如下所示:

[TestMethod]
public void TestCompileType()
{
    var code = @"
using System;
using System.Drawing;
public class CalculateClass
{
    // radian: 弧度
    // radius: 半径
    public static Point Calculate(double radian, int radius)
    {
        return new Point((int)(Math.Sin(radian) * radius), (int)(Math.Cos(radian) * radius));
    }
}";
    var compiler = new CodeCompiler();

    //添加外部程序集引用
    compiler.Assemblies.Add("system.drawing.dll");

    var type = compiler.CompileType(code, "CalculateClass");

    Assert.IsNotNull(type);
}

4、使用对象模型

  仅限于 .Net Framewor 对象模型包含代码包含的基本元素:命名空间、类型、类型成员(方法、属性、构造函数、事件等),并且包括方法实现的具体语句。CodeCompileUnit 是一个容器,逐层定义代码的结构,最后交给编译器执行。如下所示:

[TestMethod]
public void TestCompileByUnit()
{
    var compiler = new CodeCompiler();
    var unit = new CodeCompileUnit();

    var ns = new CodeNamespace("Test");
    unit.Namespaces.Add(ns);

    var td = new CodeTypeDeclaration("TestClass");
    ns.Types.Add(td);

    var md = new CodeMemberMethod();
    md.Name = "HelloWorld";
    md.Attributes = MemberAttributes.Public;
    td.Members.Add(md);

    md.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name"));
    md.Statements.Add(new CodeSnippetStatement("return "Hello " + name + ".";"));
    md.ReturnType = new CodeTypeReference(typeof(string));

    var func = compiler.CompileDelegate<Func<string, string>>(unit);

    Assert.AreEqual("Hello fireasy.", func("fireasy"));
}

5、使用外部源代码文件

  CompileAssembly、CompileType 和 CompileDelegate 方法都有一个重载,可以指定一个以上的外部源代码文件的路径。


6、使用其他语言

  仅限于 .Net Framewor 指定编译器的 CodeProvider 属性,以使用其他语言提供者来编译代码,目前 .Net Framework 只提供了 CSharpCodeProviderVBCodeProvider,默认使用 CSharpCodeProvider。如下所示,更换为 VB 语言进行编译:

[TestMethod]
public void TestCompileByVB()
{
    var code = @"
Imports System

Public Class TestClass3
    Public Sub HelloWorld()
    End Sub

    Public Function Calcuate(ByVal x As Integer, ByVal y As Integer, ByVal r As Decimal) As Decimal
        Return (x + y) * r
    End Function
End Class
";
    var compiler = new CodeCompiler();
    compiler.CodeProvider = new VBCodeProvider();

    var type = compiler.CompileType(code);

    Assert.AreEqual("TestClass3", type.Name);
}

7、输出到文件

  指定 OutputAssembly 属性,可以将编译的程序集输出到指定的路径。