2009-05-05 22 views
142

Me preguntaba si es posible guardar los fragmentos de código C# en un archivo de texto (o cualquier flujo de entrada), y luego ejecutarlos dinámicamente? Asumiendo que lo que se me proporciona compilaría bien dentro de cualquier bloque Main(), ¿es posible compilar y/o ejecutar este código? Preferiría compilarlo por motivos de rendimiento.¿Es posible compilar dinámicamente y ejecutar fragmentos de código C#?

Por lo menos, podría definir una interfaz que se les requeriría implementar, luego proporcionarían una 'sección' de código que implementó esa interfaz.

+9

Sé que este post es un par de años, pero pensé que vale la pena mencionar la introducción de la [Proyecto Roslyn] (http://msdn.microsoft.com/ en-us/vstudio/roslyn.aspx), la capacidad de compilar C# sin procesar sobre la marcha y ejecutarlo dentro de un programa .NET es un poco más fácil. – Lawrence

Respuesta

146

La mejor solución en C#/todos los lenguajes .NET estáticas es utilizar el CodeDOM para tales cosas. (Como nota, su otro propósito principal es construir dinámicamente bits de código, o incluso clases enteras.)

Aquí hay un buen ejemplo breve tomado de LukeH's blog, que también utiliza algunos LINQ solo por diversión.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using Microsoft.CSharp; 
using System.CodeDom.Compiler; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } }); 
     var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true); 
     parameters.GenerateExecutable = true; 
     CompilerResults results = csc.CompileAssemblyFromSource(parameters, 
     @"using System.Linq; 
      class Program { 
       public static void Main(string[] args) { 
       var q = from i in Enumerable.Range(1,100) 
          where i % 2 == 0 
          select i; 
       } 
      }"); 
     results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText)); 
    } 
} 

La clase de importancia primordial aquí es el CSharpCodeProvider que utiliza el compilador para compilar el código sobre la marcha. Si desea ejecutar el código, solo necesita usar un poco de reflexión para cargar dinámicamente el ensamblado y ejecutarlo.

Here es otro ejemplo en C# que (aunque un poco menos conciso) además muestra exactamente cómo ejecutar el código compilado en tiempo de ejecución utilizando el espacio de nombres System.Reflection.

+3

Aunque dudo que use Mono, pensé que podría valer la pena señalar que existe un espacio de nombres Mono.CSharp (http://www.mono-project.com/CSharp_Compiler) que en realidad contiene un compilador como servicio para que usted puede ejecutar dinámicamente código básico/evaluar expresiones en línea, con una molestia mínima. – Noldorin

+0

cuál sería una necesidad del mundo real para hacer esto. Soy bastante verde en la programación en general y creo que esto es genial, pero no puedo pensar en una razón por la que quieras/esto sería útil. Gracias si puedes explicarlo. – Crash893

+1

@ Crash893: un sistema de scripting para casi cualquier tipo de aplicación de diseñador podría hacer un buen uso de esto. Por supuesto, hay alternativas como IronPython LUA, pero esta es ciertamente una. Tenga en cuenta que un sistema de complementos se desarrollaría mejor al exponer las interfaces y cargar las DLL compiladas que contienen implementaciones de ellas, en lugar de cargar el código directamente. – Noldorin

3

Para compilar, puede iniciar una llamada de shell al compilador de csc. Es posible que tenga un dolor de cabeza tratando de mantener sus caminos y cambios correctos, pero sin duda se puede hacer.

C# Corner Shell Examples

EDITAR: O mejor aún, utilizan el CodeDOM como se sugiere noldorin ...

+0

Sí, lo bueno de CodeDOM es que puede generar el ensamblaje para usted en la memoria (así como también proporcionar mensajes de error y otra información en un formato fácil de leer). – Noldorin

+3

@Noldorin, la implementación C# CodeDOM en realidad no genera un ensamblaje en la memoria. Puede habilitar el indicador para ello, pero se ignora. Utiliza un archivo temporal en su lugar. –

+0

@Matt: Sí, es un buen punto - Olvidé ese hecho. No obstante, aún simplifica en gran medida el proceso (hace que * aparezca * como si el ensamblado se hubiera generado en la memoria) y ofrece una interfaz administrada completa, que es mucho más agradable que tratar con procesos. – Noldorin

34

Otros ya han dado buenas respuestas sobre cómo generar código en tiempo de ejecución, así que pensé que abordaría su segundo párrafo. Tengo algo de experiencia con esto y solo quiero compartir una lección que aprendí de esa experiencia.

Por lo menos, podría definir una interfaz que serían necesarios para implementar , a continuación, que proporcionarían una 'sección' código que implementa la interfaz que .

Puede tener un problema si utiliza un interface como tipo de base. Si agrega un único método nuevo al interface en el futuro, todas las clases existentes suministradas por el cliente que implementan el interface ahora se vuelven abstractas, lo que significa que no podrá compilar ni crear instancias de la clase suministrada por el cliente en el tiempo de ejecución.

Tuve este problema cuando llegó el momento de agregar un nuevo método después de aproximadamente 1 año desde el envío de la interfaz anterior y después de distribuir una gran cantidad de datos "heredados" que debían ser compatibles. Terminé creando una nueva interfaz heredada de la anterior, pero este enfoque hizo más difícil cargar y crear instancias de las clases suministradas por el cliente porque tenía que verificar qué interfaz estaba disponible.

Una solución en la que pensé en ese momento era usar una clase real como tipo de base como la siguiente. La clase en sí misma se puede marcar como abstracta, pero todos los métodos deben ser métodos virtuales vacíos (no métodos abstractos). Los clientes pueden anular los métodos que desean y puedo agregar nuevos métodos a la clase base sin invalidar el código existente proporcionado por el cliente.

public abstract class BaseClass 
{ 
    public virtual void Foo1() { } 
    public virtual bool Foo2() { return false; } 
    ... 
} 

Independientemente de si se aplica este problema se debe considerar la forma de versión de la interfaz entre su base de código y el código de cliente por el proveedor.

+2

que es una perspectiva valiosa y útil. – Cheeso

39

You can compile a piece C# of code into memory and generate assembly bytes con Roslyn. Ya se mencionó, pero valdría la pena agregar algún ejemplo de Roslyn para esto aquí. El siguiente es el ejemplo completo:

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Reflection; 
using Microsoft.CodeAnalysis; 
using Microsoft.CodeAnalysis.CSharp; 
using Microsoft.CodeAnalysis.Emit; 

namespace RoslynCompileSample 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@" 
       using System; 

       namespace RoslynCompileSample 
       { 
        public class Writer 
        { 
         public void Write(string message) 
         { 
          Console.WriteLine(message); 
         } 
        } 
       }"); 

      string assemblyName = Path.GetRandomFileName(); 
      MetadataReference[] references = new MetadataReference[] 
      { 
       MetadataReference.CreateFromFile(typeof(object).Assembly.Location), 
       MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) 
      }; 

      CSharpCompilation compilation = CSharpCompilation.Create(
       assemblyName, 
       syntaxTrees: new[] { syntaxTree }, 
       references: references, 
       options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); 

      using (var ms = new MemoryStream()) 
      { 
       EmitResult result = compilation.Emit(ms); 

       if (!result.Success) 
       { 
        IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic => 
         diagnostic.IsWarningAsError || 
         diagnostic.Severity == DiagnosticSeverity.Error); 

        foreach (Diagnostic diagnostic in failures) 
        { 
         Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); 
        } 
       } 
       else 
       { 
        ms.Seek(0, SeekOrigin.Begin); 
        Assembly assembly = Assembly.Load(ms.ToArray()); 

        Type type = assembly.GetType("RoslynCompileSample.Writer"); 
        object obj = Activator.CreateInstance(type); 
        type.InvokeMember("Write", 
         BindingFlags.Default | BindingFlags.InvokeMethod, 
         null, 
         obj, 
         new object[] { "Hello World" }); 
       } 
      } 

      Console.ReadLine(); 
     } 
    } 
} 
+0

Es el mismo código que usa el compilador de C#, que es el mayor beneficio. Complex es un término relativo, pero compilar código en tiempo de ejecución es un trabajo complejo que hacer de todos modos. Sin embargo, el código anterior no es complejo en absoluto. – tugberk

1

encontraron este artículo útil - asegura la Asamblea compilado hace referencia a todo lo que actualmente ha referenciado, ya que hay una buena probabilidad de que quería que el C# se está compilando utilizar algunas clases, etc en el código que está emitiendo el siguiente:

 var refs = AppDomain.CurrentDomain.GetAssemblies(); 
     var refFiles = refs.Where(a => !a.IsDynamic).Select(a => a.Location).ToArray(); 
     var cSharp = (new Microsoft.CSharp.CSharpCodeProvider()).CreateCompiler(); 
     var compileParams = new System.CodeDom.Compiler.CompilerParameters(refFiles); 
     compileParams.GenerateInMemory = true; 
     compileParams.GenerateExecutable = false; 

     var compilerResult = cSharp.CompileAssemblyFromSource(compileParams, code); 
     var asm = compilerResult.CompiledAssembly; 

en mi caso yo estaba emitiendo una clase, cuyo nombre fue almacenado en una cadena, className, que tenía un solo método estático público denominado Get(), que regresó con el tipo StoryDataIds. Esto es lo que llamar a ese método se parece a:

 var tempType = asm.GetType(className); 
     var ids = (StoryDataIds)tempType.GetMethod("Get").Invoke(null, null); 
Cuestiones relacionadas