运行时重新编译C#,不使用AppDomains

运行时重新编译C#,不使用AppDomains,c#,compilation,assembly.load,C#,Compilation,Assembly.load,假设我有两个C#应用程序-game.exe(XNA,需要支持Xbox 360)和editor.exe(XNA托管在WinForms中)-它们都共享一个engine.dll程序集来完成绝大部分工作 现在,让我们假设我想添加某种基于C的脚本(它不完全是“脚本”,但我会这样称呼它)。每个级别都从基类继承自己的类(我们称之为LevelController) 以下是这些脚本的重要约束: 它们必须是真实的、经过编译的C#代码 他们应该需要最少的手动“胶水”工作,如果有的话 它们必须与其他所有内容在同一App

假设我有两个C#应用程序-
game.exe
(XNA,需要支持Xbox 360)和
editor.exe
(XNA托管在WinForms中)-它们都共享一个
engine.dll
程序集来完成绝大部分工作

现在,让我们假设我想添加某种基于C的脚本(它不完全是“脚本”,但我会这样称呼它)。每个级别都从基类继承自己的类(我们称之为
LevelController

以下是这些脚本的重要约束:

  • 它们必须是真实的、经过编译的C#代码

  • 他们应该需要最少的手动“胶水”工作,如果有的话

  • 它们必须与其他所有内容在同一AppDomain中运行

  • 对于游戏来说,这非常简单:所有脚本类都可以编译成一个程序集(比如,
    levels.dll
    ),并且可以根据需要使用反射实例化各个类

    编辑要难得多。编辑器能够在编辑器窗口中“玩游戏”,然后将所有内容重置回开始位置(这就是为什么编辑器首先需要了解这些脚本)

    我试图实现的基本上是编辑器中的“重新加载脚本”按钮,它将重新编译并加载与正在编辑的级别相关联的脚本类,当用户按下“播放”按钮时,创建最近编译的脚本的实例

    其结果将是编辑器内的快速编辑测试工作流(而不是替代方案-即保存级别、关闭编辑器、重新编译解决方案、启动编辑器、加载级别、测试)


    现在我想我已经找到了一种实现这一目标的潜在方法——这本身就引出了一些问题(如下所示):

  • 将给定级别所需的
    .cs
    文件集合(或者,如果需要,将整个
    levels.dll
    项目)编译为临时的、唯一的命名程序集。该程序集需要引用
    engine.dll
    。如何在运行时以这种方式调用编译器?如何让它输出这样一个程序集(我可以在内存中这样做吗)

  • 加载新程序集。我将同名的类加载到同一个进程中有关系吗?(我的印象是这些名称是由程序集名称限定的?)

    现在,正如我提到的,我不能使用AppDomains。但是,另一方面,我不介意泄露旧版本的脚本类,因此卸载功能并不重要。除非是这样?我假设加载几百个程序集是可行的

  • 在播放level时,从刚刚加载的特定程序集继承LevelController的类。如何做到这一点

  • 最后:

    这是明智的做法吗?有没有更好的办法



    更新:这些天我用a来解决潜在的问题。

    好吧,你希望能够动态编辑东西,对吗?这就是你的目标,不是吗

    当您编译程序集并加载它们时,现在有办法卸载它们,除非您卸载AppDomain

    您可以使用Assembly.load方法加载预编译的程序集,然后通过反射调用入口点

    我将考虑动态装配方法。通过当前AppDomain,您可以说希望创建动态程序集。这就是DLR(动态语言运行时)的工作方式。使用动态程序集,您可以创建实现某些可见接口的类型,并通过该接口调用它们。使用动态程序集的背后是你必须自己提供正确的IL,你不能简单地用内置的.NET编译器生成它,但是,我打赌Mono项目有一个C#编译器实现,你可能想检查一下。他们已经有了一个C#解释器,它读取C#源文件并编译并执行,这肯定是通过System.Reflection.Emit API处理的


    不过,我不确定这里的垃圾收集,因为当涉及到动态类型时,我认为运行时不会释放它们,因为它们可以随时被引用。只有当动态程序集本身被销毁并且不存在对该程序集的引用时,才能合理地释放该内存。如果正在和正在生成大量代码,请确保内存在某个时候由GC收集。

    查看Microsoft.CSharp.CSharpCodeProvider和System.CodeDom.Compiler周围的名称空间

    编译.cs文件的集合

    应该很简单,就像

    我将同名的类加载到同一个进程中有关系吗

    一点也不。只是名字

    实例从LevelController继承的类


    加载您创建的程序集,如assembly.Load等。使用反射查询您要实例化的类型。获取构造函数并调用它。

    现在有了一个相当优雅的解决方案,这是由(a)在.NET 4.0中的一个新特性和(b)Roslyn实现的

    可收藏组件 在.NET 4.0中,您可以在定义动态程序集时指定
    AssemblyBuilderAccess.RunAndCollect
    ,这使得动态程序集可以垃圾回收:

    AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
        new AssemblyName("Foo"), AssemblyBuilderAccess.RunAndCollect);
    
    对于香草.NET4.0,我认为您需要通过在原始IL中编写方法来填充动态程序集

    罗斯林 输入Roslyn:Roslyn可以将原始C#代码编译成动态程序集。以下是一个示例,受这两个示例的启发,已更新以使用最新的Roslyn二进制文件:

    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    using Roslyn.Compilers;
    using Roslyn.Compilers.CSharp;
    
    namespace ConsoleApplication1
    {
        public static class Program
        {
            private static Type CreateType()
            {
                SyntaxTree tree = SyntaxTree.ParseText(
                    @"using System;
    
                    namespace Foo
                    {
                        public class Bar
                        {
                            public static void Test()
                            {
                                Console.WriteLine(""Hello World!"");
                            }
                        }
                    }");
    
                var compilation = Compilation.Create("Hello")
                    .WithOptions(new CompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                    .AddReferences(MetadataReference.CreateAssemblyReference("mscorlib"))
                    .AddSyntaxTrees(tree);
    
                ModuleBuilder helloModuleBuilder = AppDomain.CurrentDomain
                    .DefineDynamicAssembly(new AssemblyName("FooAssembly"), AssemblyBuilderAccess.RunAndCollect)
                    .DefineDynamicModule("FooModule");
                var result = compilation.Emit(helloModuleBuilder);
    
                return helloModuleBuilder.GetType("Foo.Bar");
            }
    
            static void Main(string[] args)
            {
                Type fooType = CreateType();
                MethodInfo testMethod = fooType.GetMethod("Test");
                testMethod.Invoke(null, null);
    
                WeakReference weak = new WeakReference(fooType);
    
                fooType = null;
                testMethod = null;
    
                Console.WriteLine("type = " + weak.Target);
                GC.Collect();
                Console.WriteLine("type = " + weak.Target);
    
                Console.ReadKey();
            }
        }
    }
    

    总之:使用collectible assembly和Roslyn,您可以将C#代码编译成一个程序集,该程序集可以加载到
    AppDomain
    ,然后进行垃圾收集(取决于许多限制)。

    如果语言是Java,那么答案将是使用JRebel。从那以后我