How can I prevent CompileAssemblyFromSource from leaking memory?

孤人 提交于 2019-11-27 03:24:26
toad

Unloading an assembly is not supported. Some information on why can be found here. Some information on using an AppDomain can be found here.

I think I have a working solution. Thanks to everyone for pointing me in the right direction (I hope).

Assemblies can't be unloaded directly, but AppDomains can. I created a helper library that gets loaded in a new AppDomain and is able to compile a new assembly from code. Here's what the class in that helper library looks like:

public class CompilerRunner : MarshalByRefObject
{
    private Assembly assembly = null;

    public void PrintDomain()
    {
        Console.WriteLine("Object is executing in AppDomain \"{0}\"",
            AppDomain.CurrentDomain.FriendlyName);
    }

    public bool Compile(string code)
    {
        CSharpCodeProvider codeProvider = new CSharpCodeProvider();
        CompilerParameters parameters = new CompilerParameters();
        parameters.GenerateInMemory = true;
        parameters.GenerateExecutable = false;
        parameters.ReferencedAssemblies.Add("system.dll");

        CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);
        if (!results.Errors.HasErrors)
        {
            this.assembly = results.CompiledAssembly;
        }
        else
        {
            this.assembly = null;
        }

        return this.assembly != null;
    }

    public object Run(string typeName, string methodName, object[] args)
    {
        Type type = this.assembly.GetType(typeName);
        return type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, assembly, args);
    }

}

It's very basic, but was enough for testing. PrintDomain is there to verify that it does live in my new AppDomain. Compile takes some source code and tries to create an assembly. Run lets us test executing static methods from the given source code.

Here's how I use the helper library:

static void CreateCompileAndRun()
{
    AppDomain domain = AppDomain.CreateDomain("MyDomain");

    CompilerRunner cr = (CompilerRunner)domain.CreateInstanceFromAndUnwrap("CompilerRunner.dll", "AppDomainCompiler.CompilerRunner");            
    cr.Compile("public class Hello { public static string Say() { return \"hello\"; } }");            
    string result = (string)cr.Run("Hello", "Say", new object[0]);

    AppDomain.Unload(domain);
}

It basically creates the domain, creates an instance of my helper class (CompilerRunner), uses it to compile a new assembly (hidden), runs some code from that new assembly, and then unloads the domain to free up memory.

You'll notice the use of MarshalByRefObject and CreateInstanceFromAndUnwrap. These are important for ensuring that the helper library really does live in the new domain.

If anyone notices any problems or has suggestions for improving this, I'd love to hear them.

You may also find this blog entry useful: Using AppDomain to Load and Unload Dynamic Assemblies. It provides some example code demonstrating how create an AppDomain, load a (dynamic) assembly into it, do some work in the new AppDomain then unload it.

Edit: fixed link as pointed out in comments below.

Can you wait until .NET 4.0? With it you can use expression trees and the DLR to dynamically generate code without the code-gen memory loss issue.

Another option is to use .NET 3.5 with a dynamic language like IronPython.

EDIT: Expression Tree Example

http://www.infoq.com/articles/expression-compiler

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!