假设我有两个 C# 应用程序 - game.exe
(XNA,需要支持 Xbox 360)和 editor.exe
(XNA 托管在 WinForms 中)- 它们两者共享一个完成大部分工作的 engine.dll
程序集。
现在假设我想添加某种基于 C# 的脚本(它不完全是“脚本”,但我会这样调用它)。每个级别都有自己的类继承自基类(我们称之为 LevelController
)。
这些是这些脚本的重要约束:
它们需要是真实的编译 C# 代码
他们应该需要最少的手动“胶水”工作,如果有的话
它们必须与其他一切运行在同一个 AppDomain 中
对于游戏 - 这非常简单:所有脚本类都可以编译成一个程序集(例如,levels.dll
),并且可以根据需要使用反射实例化各个类。
编辑器就难多了。编辑器可以在编辑器窗口中“玩游戏”,然后将所有内容重置回开始的位置(这就是编辑器需要首先了解这些脚本的原因)。
我想要实现的基本上是编辑器中的“重新加载脚本”按钮,它将重新编译并加载与正在编辑的级别关联的脚本类,并且当用户按下“播放”按钮时,创建一个实例最近编译的脚本。
其结果将是编辑器内的快速编辑-测试工作流程(而不是替代方案 - 保存关卡、关闭编辑器、重新编译解决方案、启动编辑器、加载关卡、测试)。
现在我认为我已经找到了实现这一目标的潜在方法 - 这本身会导致许多问题(如下所示):
将给定级别所需的
.cs
文件集合(或者,如果需要,整个levels.dll
项目)编译成一个临时的,唯一命名的程序集。该程序集将需要引用engine.dll
。如何在运行时以这种方式调用编译器?如何让它输出这样的程序集(我可以在内存中做)吗?加载新程序集。我将具有相同名称的类加载到同一进程中是否重要? (我的印象是名称由程序集名称限定?)
现在,正如我提到的,我不能使用 AppDomains。但是,另一方面,我不介意泄露旧版本的脚本类,所以卸载能力并不重要。除非是吗?我假设加载几百个程序集是可行的。
播放关卡时,从刚刚加载的特定程序集中实例化从 LevelController 继承的类。如何做到这一点?
最后:
这是明智的做法吗?可以用更好的方法来完成吗?
更新:最近我使用 far simpler approach解决根本问题。
最佳答案
现在有一个相当优雅的解决方案,通过 (a) .NET 4.0 中的新功能和 (b) Roslyn 使之成为可能。
收藏集
在 .NET 4.0 中,您可以在定义动态程序集时指定 AssemblyBuilderAccess.RunAndCollect
,这使得动态程序集可被垃圾回收:
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("Foo"), AssemblyBuilderAccess.RunAndCollect);
对于 vanilla .NET 4.0,我认为您需要通过在原始 IL 中编写方法来填充动态程序集。
罗斯林
进入 Roslyn:Roslyn 可让您将原始 C# 代码编译成动态程序集。这是一个例子,灵感来自这两个 blog posts ,已更新以使用最新的 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();
}
}
}
总结:借助可收集的程序集和 Roslyn,您可以将 C# 代码编译成可以加载到 AppDomain
中的程序集,然后进行垃圾收集(以 rules 为准)。
关于c# - 在运行时重新编译 C#,没有 AppDomains,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1353456/