假设我有两个C#应用程序 - game.exe(XNA,需要支持Xbox 360)和editor.exe(在WinForms中托管的XNA) - 它们共享一个engine.dll可以完成绝大部分工作的程序集.
现在让我们说我想添加一些基于C#的脚本(它不是"脚本",但我会称之为).每个级别都有自己继承自基类的类(我们称之为LevelController).
这些是这些脚本的重要约束:
它们需要是真实的,编译好的C#代码
他们应该需要最少的手动"粘合"工作,如果有的话
它们必须与其他所有内容在同一个AppDomain中运行
对于游戏 - 这非常简单:所有脚本类都可以编译成一个程序集(比如说levels.dll),并且可以根据需要使用反射来实例化各个类.
编辑要困难得多.编辑器能够在编辑器窗口中"玩游戏",然后将所有内容重置回原来的位置(这就是编辑器首先需要了解这些脚本的原因).
我想要实现的基本上是编辑器中的"重新加载脚本"按钮,它将重新编译并加载与正在编辑的级别相关联的脚本类,当用户按下"播放"按钮时,创建最近的实例编译脚本.
其结果将是编辑器中的快速编辑测试工作流程(而不是替代方案 - 保存级别,关闭编辑器,重新编译解决方案,启动编辑器,加载级别,测试).
现在我想我已经找到了实现这个目标的潜在方法 - 这本身就会产生一些问题(如下所示):
.cs将给定级别(或者,如果需要,整个levels.dll项目)所需的文件集合编译为临时的,唯一命名的程序集.那个集会需要参考engine.dll.如何在运行时以这种方式调用编译器?如何让它输出这样的程序集(我可以在内存中执行)吗?
加载新装配.我在同一个进程中加载具有相同名称的类是否重要?(我的印象是名称是由程序集名称限定的?)
现在,正如我所提到的,我无法使用AppDomains.但是,另一方面,我不介意泄漏旧版本的脚本类,因此卸载的能力并不重要.除非是吗?我假设加载可能几百个组件是可行的.
在播放关卡时,实例是从刚加载的特定程序集继承自LevelController的类.这该怎么做?
最后:
这是一种明智的做法吗?它可以做得更好吗?
更新:这些天我使用一种更简单的方法来解决潜在的问题.
我注意到Roslyn解析/编译的启动时间是一个相当重要的一次性成本.编辑:我正在使用Roslyn CTP MSI(程序集在GAC中).这是预期的吗?有没有解决方法?
运行下面的代码与1次迭代(约3秒)和300次迭代(约3秒)的时间几乎相同.
[Test]
public void Test()
{
var iters = 300;
foreach (var i in Enumerable.Range(0, iters))
{
// Parse the source file using Roslyn
SyntaxTree syntaxTree = SyntaxTree.ParseText(@"public class Foo" + i + @" { public void Exec() { } }");
// Add all the references we need for the compilation
var references = new List<MetadataReference>();
references.Add(new MetadataFileReference(typeof(int).Assembly.Location));
var compilationOptions = new CompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary);
// Note: using a fixed assembly name, which doesn't matter as long as …Run Code Online (Sandbox Code Playgroud) 从编译Roslyn SyntaxTree的许多示例中,我看到了以下代码:
[... create tree and compilation ...]
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("foo"), AssemblyBuilderAccess.RunAndCollect);
var module = assembly.DefineDynamicModule("foo");
var result = compilation.Emit(module);
Run Code Online (Sandbox Code Playgroud)
但是当我尝试在当前使用Visual Studio 2015 RC发布的Roslyn中执行此操作时,我看到没有Emit()哪个需要一个模块.我需要写入流并将其加载到常规AppDomain锁定中Assembly.
然后我从Tomas Matousek看到这个答案:https://stackoverflow.com/a/22977158
他说这已被删除,因为它"有问题".
我很难在互联网上找到关于此的任何其他信息.有谁知道: