比方说,我有两个C#应用程序- game.exe
(XNA,需要支持的Xbox 360)和editor.exe
(XNA中的WinForms托管) -它们共用一个engine.dll
组件做绝大多数的工作。
现在,让我们说,我想补充某种C#为基础的脚本(这是不是很“脚本”,但我会称呼它)。 每个级别会从一个基类继承自己的类(我们把它叫做LevelController
)。
这些是这些脚本的重要制约因素:
他们需要的是真实的,编译的C#代码
他们应该要求最少的人工“胶水”的工作,如果有的话
他们必须在同一个AppDomain中运行的一切
对于游戏-这是非常简单的:所有的脚本类可以被编译成一个组件(比如, levels.dll
)和个别类可以根据需要使用反射来实例化。
编辑更难。 编辑器有“玩游戏”的编辑器窗口中,然后重置一切回到它开始(这就是为什么编辑器需要知道摆在首位这些脚本)的能力。
我想实现基本上是将重新编译与负载水平相关联的脚本类的编辑器“重新加载脚本”按钮,正在编辑,当用户按下“播放”按钮,创建的最近一个实例编译脚本。
其中的结果将是编辑器(而不是替代 - 这是节约的水平,关闭编辑器,重新编译该溶液中,启动编辑器,加载水平,测试)内的快速编辑测试工作流程。
现在,我想我已经制定了实现这一目标的潜在方法-这本身导致了一些问题(如下):
编译的集合.cs
对于给定级别所需的文件(或者,如果需要的话,整个levels.dll
项目)到一个临时的,独特的命名程序集。 这次大会将要引用engine.dll
。 如何调用编译器在运行时这样? 如何得到它输出这样的组件(和我能做到这一点在内存中)?
加载新的装配。 这会事,我加载类具有相同的名称到同一个过程? (我的印象是,名称由程序集名称限定下?)
现在,正如我所说,我不能使用应用程序域。 但是,在另一方面,我不介意泄漏脚本类的旧版本,所以要卸载的能力并不重要。 除非它是什么? 我猜想,也许几百装载组件是可行的。
当播放水平,例如是从LevelController继承刚加载的特定组件的类。 这该怎么做?
最后:
这是一个明智的做法? 难道做一个更好的办法?
UPDATE:这几天我用一个简单得多的方式来解决根本问题。
看看周围Microsoft.CSharp.CSharpCodeProvider和System.CodeDom.Compiler的命名空间。
编译的.cs文件集合
应该是相当简单的像http://support.microsoft.com/kb/304655
这会事,我加载类具有相同的名称到同一个过程?
一点也不。 这只是名称。
实例从LevelController继承的类。
加载您创建类似Assembly.Load等查询你想使用反射来实例化的类型的程序集。 获取构造函数,并调用它。
现在有一个比较完美的解决方案,通过(a)在.NET 4.0中的新功能,以及(b)罗斯林成为可能。
珍藏组件
在.NET 4.0中,你可以指定AssemblyBuilderAccess.RunAndCollect
定义动态程序,这使得动态组装垃圾收藏时:
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("Foo"), AssemblyBuilderAccess.RunAndCollect);
香草.NET 4.0,我认为你需要通过编写原IL方法来填充动态装配。
罗斯林
进入罗斯林:罗斯林让您编译原始的C#代码到一个动态组装。 下面是一个例子,这两个启发博客 帖子 ,更新了最新的罗斯林二进制文件的工作:
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();
}
}
}
总结:与收集的组件和罗斯林,可以编译C#代码转换成可以被加载到一个组件AppDomain
,然后垃圾回收(受到众多的规则 )。
那么,你希望能够在飞行中编辑的东西,对不对? 这是你的目标在这里不是吗?
当您编译组件和加载它们现在有办法,除非你卸下你的AppDomain卸载它们。
可以加载预编译组件与Assembly.Load方法,然后通过反射调用的入口点。
我会考虑的动态组装方式。 当你通过你当前的AppDomain说,你想创建一个动态组装。 这是DLR(动态语言运行时)是如何工作的。 有了动态组件,您可以创建实现一些可视化界面,并呼吁他们通过类型。 动态装配体的背面是,你必须提供正确IL自己,你不能简单地产生与内置的.NET编译器,但我敢打赌,Mono项目有你可能想看看一个C#编译器实现。 他们已经有了一个C#解释器读取在C#源文件和编译出并执行它,这绝对通过System.Reflection.Emit API处理。
我不知道的垃圾收集在这里,但因为当它涉及到动态类型,我认为运行时不释放他们,因为他们可以在任何时候被引用。 只有当动态组件本身被破坏,没有引用存在于组装会是合理释放内存。 如果你并重新生成大量的代码,确保内存,在某些时候,由GC回收。
如果语言是Java的答案是使用JRebel的。 既然不是,答案是筹集足够的噪音,显示有需求这一点。 这可能需要某种替代的CLR语言或C#的发动机项目模板“和VS IDE的协调等工作
我怀疑有很多的场景中,这是“必须有”,但有很多它会节省很多时间,因为你可以逃脱较少的基础设施和更快的周转的事情上是不会被长期使用。 (是有一些谁认为过度工程师的事,因为他们会被使用超过20年,但与这个问题是当你需要做一些巨大变化,这很可能是因为从头开始重建整个事情一样昂贵。所以它归结为现在或以后是否要花钱的。因为它不能肯定该项目将成为业务后的关键,可能需要大的变化后,无论如何,这种观点我是用“吻” -principle并具有复杂性的在IDE中,CLR /运行实时编辑等等,而不是建设成每个应用它以后可能是有用的,当然会需要一些防御性编程和做法,修改使用这种功能的一些现场服务。作为二郎开发者都表示要干什么)