【问题标题】:Compiling code dynamically using C#使用 C# 动态编译代码
【发布时间】:2011-06-10 17:44:46
【问题描述】:

如何编写 C# 代码来编译和运行动态生成的 C# 代码。周围有例子吗?

我所追求的是动态构建一个(或多个)C# 类并在运行时运行它们。我希望生成的类与其他非动态的 C# 类交互。

我见过生成 exe 或 dll 文件的示例。我不是在那之后,我只是想让它在内存中编译一些 C# 代码然后运行它。例如,

所以这是一个非动态类,它将在我的 C# 程序集中定义,并且只会在编译时更改,

public class NotDynamicClass
{
    private readonly List<string> values = new List<string>();

    public void AddValue(string value)
    {
        values.Add(value);
    }

    public void ProcessValues()
    {
        // do some other stuff with values
    }
}

这是我的动态课程。我的 C# 代码会生成这个类并运行它,

public class DynamicClass
{
    public static void Main()
    {
        NotDynamicClass class = new NotDynamicClass();

        class.AddValue("One");
        class.AddValue("two");
    }
}

所以结果是最后我的非动态代码会调用 ProcessValues 并且它会做一些其他的事情。动态代码的重点是允许我们或客户在软件中添加自定义逻辑。

谢谢。

【问题讨论】:

  • 编译“插件”程序集并在运行时加载这些插件而不是动态编译东西是否可行?
  • 对,是的,我之前为另一个项目做过。这是可能的,但不是我们在这个项目中真正追求的。我会考虑的,谢谢。

标签: .net dynamic c#-4.0


【解决方案1】:

两种可能:

  1. Reflection.Emit
  2. System.CodeDom.Compiler

更新:

作为 cmets 部分的请求,这里有一个完整的示例,说明了如何使用 Reflection.Emit 来动态构建一个类并为其添加一个静态方法:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

public class NotDynamicClass
{
    private readonly List<string> values = new List<string>();

    public void AddValue(string value)
    {
        values.Add(value);
    }

    public void ProcessValues()
    {
        foreach (var item in values)
        {
            Console.WriteLine(item);
        }
    }
}

class Program
{
    public static void Main()
    {
        var assemblyName = new AssemblyName("DynamicAssemblyDemo");
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, false);
        var typeBuilder = moduleBuilder.DefineType("DynamicClass", TypeAttributes.Public);

        var methodBuilder = typeBuilder.DefineMethod(
            "Main",
            MethodAttributes.Public | MethodAttributes.Static,
            null,
            new Type[0]
        );

        var il = methodBuilder.GetILGenerator();
        var ctor = typeof(NotDynamicClass).GetConstructor(new Type[0]);
        var addValueMi = typeof(NotDynamicClass).GetMethod("AddValue");
        il.Emit(OpCodes.Newobj, ctor);
        il.Emit(OpCodes.Stloc_0);
        il.DeclareLocal(typeof(NotDynamicClass));
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ldstr, "One");
        il.Emit(OpCodes.Callvirt, addValueMi);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ldstr, "Two");
        il.Emit(OpCodes.Callvirt, addValueMi);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Callvirt, typeof(NotDynamicClass).GetMethod("ProcessValues"));
        il.Emit(OpCodes.Ret);
        var t = typeBuilder.CreateType();
        var mi = t.GetMethod("Main");
        mi.Invoke(null, new object[0]);
    }
}

您可以在 not NotDynamicClass 方法中放置断点,然后查看它们是如何被调用的。


更新 2:

以下是 CodeDom 编译器的示例:

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using Microsoft.CSharp;

public class NotDynamicClass
{
    private readonly List<string> values = new List<string>();

    public void AddValue(string value)
    {
        values.Add(value);
    }

    public void ProcessValues()
    {
        foreach (var item in values)
        {
            Console.WriteLine(item);
        }
    }
}

class Program
{
    public static void Main()
    {
        var provider = CSharpCodeProvider.CreateProvider("c#");
        var options = new CompilerParameters();
        var assemblyContainingNotDynamicClass = Path.GetFileName(Assembly.GetExecutingAssembly().Location);
        options.ReferencedAssemblies.Add(assemblyContainingNotDynamicClass);
        var results = provider.CompileAssemblyFromSource(options, new[] 
        { 
@"public class DynamicClass
{
    public static void Main()
    {
        NotDynamicClass @class = new NotDynamicClass();
        @class.AddValue(""One"");
        @class.AddValue(""Two"");
        @class.ProcessValues();
    }
}"
        });
        if (results.Errors.Count > 0)
        {
            foreach (var error in results.Errors)
            {
                Console.WriteLine(error);
            }
        }
        else
        {
            var t = results.CompiledAssembly.GetType("DynamicClass");
            t.GetMethod("Main").Invoke(null, null);
        }
    }
}

【讨论】:

  • 你有什么好的例子可以推荐给我上面想要做的事情吗?
  • @peter,请查看我的更新。我添加了一个示例,该示例说明了如何通过向其添加方法并在最后调用这些方法来在运行时动态构建一个类。
  • 谢谢。这就是我所害怕的,也是我在别处看到的。您正在使用 GetILGenerator 构建一个方法并调用 il.Emit 等。难道没有办法只编写代码,指定一个包含代码的字符串,然后说“运行”更像我上面的第二个代码示例。原因是我们希望将这些脚本编写为“文本”并将它们存储在数据库中。
  • @peter,请看我的第二个更新。添加了 CodeDom 编译器的示例。
  • 谢谢,完美。这看起来简直太棒了。完全不能错!
【解决方案2】:

Mono 有一个 C# compiler as a service,可用于 Windows 上的 .NET 应用程序(当然还有 Linux)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-03
    • 2013-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多