运行时重新编译C#,不使用AppDomains
假设我有两个C#应用程序-运行时重新编译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
game.exe
(XNA,需要支持Xbox 360)和editor.exe
(XNA托管在WinForms中)-它们都共享一个engine.dll
程序集来完成绝大部分工作
现在,让我们假设我想添加某种基于C的脚本(它不完全是“脚本”,但我会这样称呼它)。每个级别都从基类继承自己的类(我们称之为LevelController
)
以下是这些脚本的重要约束:
levels.dll
),并且可以根据需要使用反射实例化各个类
编辑要难得多。编辑器能够在编辑器窗口中“玩游戏”,然后将所有内容重置回开始位置(这就是为什么编辑器首先需要了解这些脚本)
我试图实现的基本上是编辑器中的“重新加载脚本”按钮,它将重新编译并加载与正在编辑的级别相关联的脚本类,当用户按下“播放”按钮时,创建最近编译的脚本的实例
其结果将是编辑器内的快速编辑测试工作流(而不是替代方案-即保存级别、关闭编辑器、重新编译解决方案、启动编辑器、加载级别、测试)
现在我想我已经找到了一种实现这一目标的潜在方法——这本身就引出了一些问题(如下所示):
.cs
文件集合(或者,如果需要,将整个levels.dll
项目)编译为临时的、唯一的命名程序集。该程序集需要引用engine.dll
。如何在运行时以这种方式调用编译器?如何让它输出这样一个程序集(我可以在内存中这样做吗)更新:这些天我用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。从那以后我