¿Cómo puedo evitar que CompileAssemblyFromSource pierda memoria?

Tengo un código C # que usa CSharpCodeProvider.CompileAssemblyFromSource para crear un ensamblaje en la memoria. Después de que el ensamblado se recolectó como basura, mi aplicación usa más memoria de la que tenía antes de crear el ensamblaje. Mi código está en una aplicación web ASP.NET, pero he duplicado este problema en WinForm. Estoy usando System.GC.GetTotalMemory (true) y Red Gate ANTS Memory Profiler para medir el crecimiento (alrededor de 600 bytes con el código de muestra).

A partir de la búsqueda que hice, parece que la filtración proviene de la creación de nuevos tipos, en realidad no de ningún objeto al que tenga referencias. Algunas de las páginas web que he encontrado han mencionado algo sobre AppDomain, pero no entiendo. ¿Puede alguien explicar qué está pasando aquí y cómo solucionarlo?

Aquí hay un código de muestra para fugas:

private void leak() { CSharpCodeProvider codeProvider = new CSharpCodeProvider(); CompilerParameters parameters = new CompilerParameters(); parameters.GenerateInMemory = true; parameters.GenerateExecutable = false; parameters.ReferencedAssemblies.Add("system.dll"); string sourceCode = "using System;\r\n"; sourceCode += "public class HelloWord {\r\n"; sourceCode += " public HelloWord() {\r\n"; sourceCode += " Console.WriteLine(\"hello world\");\r\n"; sourceCode += " }\r\n"; sourceCode += "}\r\n"; CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, sourceCode); Assembly assembly = null; if (!results.Errors.HasErrors) { assembly = results.CompiledAssembly; } } 

Actualización 1: esta pregunta puede estar relacionada: cargar y descargar dinámicamente a dll generado utilizando CSharpCodeProvider

Actualización 2: tratando de entender los dominios de las aplicaciones más, encontré esto: ¿Qué es un dominio de aplicación? Una explicación para los principiantes de .Net

Actualización 3: para aclarar, estoy buscando una solución que proporcione la misma funcionalidad que el código anterior (comstackr y proporcionar acceso al código generado) sin pérdida de memoria. Parece que la solución implicará la creación de un nuevo dominio de aplicación y clasificación.

Descargar un ensamblaje no es compatible. Parte de la información sobre por qué se puede encontrar aquí . Parte de la información sobre el uso de un dominio de aplicación se puede encontrar aquí .

Creo que tengo una solución de trabajo. Gracias a todos por apuntarme en la dirección correcta (espero).

Los ensamblajes no se pueden descargar directamente, pero AppDomains sí. Creé una biblioteca de ayuda que se carga en un nuevo AppDomain y puede comstackr un nuevo ensamblado a partir del código. Así es como se ve la clase en esa biblioteca auxiliar:

 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); } } 

Es muy básico, pero fue suficiente para las pruebas. PrintDomain está ahí para verificar que viva en mi nuevo AppDomain. La comstackción toma un código fuente e intenta crear un ensamblaje. Ejecutar nos permite probar la ejecución de métodos estáticos a partir del código fuente dado.

Así es como uso la biblioteca auxiliar:

 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); } 

Básicamente crea el dominio, crea una instancia de mi clase de ayuda (CompilerRunner), lo usa para comstackr un nuevo ensamblaje (oculto), ejecuta un código de ese nuevo ensamblaje y luego descarga el dominio para liberar memoria.

Notará el uso de MarshalByRefObject y CreateInstanceFromAndUnwrap. Estos son importantes para garantizar que la biblioteca auxiliar realmente viva en el nuevo dominio.

Si alguien nota algún problema o tiene sugerencias para mejorar esto, me encantaría escucharlos.

También puede encontrar esta entrada de blog útil: Usar AppDomain para cargar y descargar ensambles dynamics. Proporciona un ejemplo de código que demuestra cómo crear un AppDomain, cargar un ensamblado (dynamic) en él, hacer algo de trabajo en el nuevo AppDomain y luego descargarlo.

Editar: enlace fijo como se señala en los comentarios a continuación.

¿Puedes esperar hasta .NET 4.0? Con él puede usar árboles de expresión y el DLR para generar código dinámicamente sin el problema de pérdida de memoria de código genérico.

Otra opción es usar .NET 3.5 con un lenguaje dynamic como IronPython.

EDITAR: Ejemplo de árbol de expresiones

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