2009-05-05 13 views
142

Zastanawiam się, czy możliwe jest zapisanie fragmentów kodu C# do pliku tekstowego (lub dowolnego strumienia wejściowego), a następnie wykonać je dynamicznie? Zakładając, że to, co mi zostało dostarczone, skompilowałoby dobrze w dowolnym bloku Main(), czy możliwe jest skompilowanie i/lub wykonanie tego kodu? Wolałbym skompilować go ze względu na wydajność.Czy jest możliwe dynamiczne kompilowanie i wykonywanie fragmentów kodu C#?

Przynajmniej mogłem zdefiniować interfejs, że będą wymagane do zrealizowania, wtedy oni zapewnić „sekcja” kodu, który realizowany ten interfejs.

+9

Znam ten post jest kilka lat, ale uważam, że warto wspomnieć o wprowadzeniu [Project] (http://msdn.microsoft.com/ Roslyn en-us/vstudio/roslyn.aspx), możliwość kompilowania surowego C# w locie i uruchamiania go w programie .NET jest trochę łatwiejsza. – Lawrence

Odpowiedz

146

najlepszego rozwiązania w C#/.NET wszystkich statycznych języków jest użycie CodeDOM do takich rzeczy. (Na marginesie, jego drugim głównym celem jest dynamiczne konstruowanie bitów kodu, a nawet całych klas.)

Oto ładny krótki przykład z LukeH's blog, który wykorzystuje trochę LINQ również dla zabawy.

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

Klasa podstawowym znaczeniu tutaj jest CSharpCodeProvider który wykorzystuje kompilator do kompilacji kodu w locie. Jeśli chcesz uruchomić kod, wystarczy trochę refleksji, aby dynamicznie załadować złożenie i wykonać go.

Here to kolejny przykład w języku C#, który (choć nieco mniej zwięzły) dodatkowo pokazuje dokładnie, jak uruchomić skompilowany kod środowiska wykonawczego przy użyciu przestrzeni nazw System.Reflection.

+3

Chociaż wątpię, że używasz Mono, pomyślałem, że warto byłoby wskazać, że istnieje przestrzeń nazw Mono.CSharp (http://www.mono-project.com/CSharp_Compiler), która faktycznie zawiera kompilator jako usługę, dzięki czemu potrafi dynamicznie uruchamiać podstawowy kod/oceniać wyrażenia inline, przy minimalnym wysiłku. – Noldorin

+0

jaka byłaby realna potrzeba tego świata. Ogólnie jestem programistą i myślę, że to jest fajne, ale nie mogę wymyślić powodu, dla którego chciałbyś/byłoby to przydatne. Dzięki, jeśli potrafisz to wyjaśnić. – Crash893

+1

@ Crash893: System skryptów dla prawie każdego rodzaju aplikacji projektanta może zrobić dobry użytek z tego. Oczywiście istnieją alternatywy, takie jak IronPython LUA, ale jest to z pewnością jedna. Zauważ, że system wtyczek zostałby lepiej rozwinięty poprzez udostępnianie interfejsów i ładowanie skompilowanych bibliotek DLL, które zawierają ich implementacje, zamiast bezpośredniego ładowania kodu. – Noldorin

3

Do kompilacji można po prostu zainicjować połączenie powłoki do kompilatora CSC. Możesz mieć ból głowy próbujący utrzymać swoje ścieżki i przełączniki prosto, ale na pewno można to zrobić.

C# Corner Shell Examples

EDIT: Albo jeszcze lepiej, użyj CodeDOM jak sugeruje Noldorin ...

+0

Dobra rzecz z CodeDOM polega na tym, że może on wygenerować zestaw dla ciebie w pamięci (jak również dostarczanie komunikatów o błędach i innych informacji w łatwo czytelnym formacie). – Noldorin

+3

@Noldorin, Implementacja CodeDOM w C# w rzeczywistości nie generuje zespołu w pamięci. Możesz włączyć dla niego flagę, ale zostanie ona zignorowana. Zamiast tego używa pliku tymczasowego. –

+0

@Matt: Tak, dobra uwaga - zapomniałem o tym fakcie. Niemniej jednak znacznie upraszcza proces (sprawia, że ​​jest on efektywny * pojawia się * tak, jakby zespół był generowany w pamięci) i oferuje kompletny interfejs zarządzany, który jest o wiele lepszy niż przetwarzanie procesów. – Noldorin

34

Inni już dał dobre odpowiedzi, w jaki sposób generowania kodu w czasie wykonywania tak myślałem, że chciałbym zaadresować akapit drugi. Mam pewne doświadczenie z tym i chcę po prostu podzielić się lekcją, której nauczyłem się z tego doświadczenia.

przynajmniej mogłem zdefiniować interfejs, że będą wymagane do zrealizowania, wtedy oni zapewnić a „sekcja” kod, który realizowany że interfejs.

Możesz mieć problem, jeśli używasz interface jako typu podstawowego. Jeśli dodać jedną nową metodę do interface w przyszłości wszystkie istniejące klasy klienckie dostarczone które implementują interface teraz stają się abstrakcyjne, co oznacza, że ​​nie będzie w stanie skompilować lub instancję klasy klienta dostarczane w czasie wykonywania.

Miałem ten problem, gdy nadszedł czas, aby dodać nową metodę po około 1 roku od wysyłki starego interfejsu i po dystrybucji dużej ilości "starszych" danych, które musiały być obsługiwane. W końcu stworzyłem nowy interfejs, który odziedziczył po starym, ale to podejście utrudniło ładowanie i tworzenie instancji klas dostarczonych przez klienta, ponieważ musiałem sprawdzić, który interfejs był dostępny.

Jednym z rozwiązań, o których myślałem wtedy, było użycie rzeczywistej klasy jako typu podstawowego, takiego jak ten poniżej. Samą klasę można oznaczyć jako abstrakcyjną, ale wszystkie metody powinny być pustymi wirtualnymi metodami (a nie metodami abstrakcyjnymi). Klienci mogą wtedy zastąpić żądane metody i mogę dodać nowe metody do klasy bazowej bez unieważniania istniejącego kodu dostarczonego przez klienta.

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

Bez względu na to, czy ten problem dotyczy, należy rozważyć, jak przeprowadzić wersję interfejsu między bazą kodu a kodem dostarczonym przez klienta.

+2

to cenna, pomocna perspektywa. – Cheeso

39

You can compile a piece C# of code into memory and generate assembly bytes z Roslyn. Jest już wspomniany, ale warto tutaj dodać przykład Roslyn. Poniżej znajduje się pełna przykład:

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

Jest to ten sam kod, którego używa kompilator C#, co jest największą korzyścią. Complex jest terminem względnym, ale kompilowanie kodu w środowisku wykonawczym jest skomplikowanym zadaniem. Powyższy kod nie jest jednak w ogóle złożony. – tugberk

1

Znaleziony to użyteczne - zapewnia skompilowany Zgromadzenie odwołuje wszystko, czego obecnie nie odwołuje, ponieważ istnieje duża szansa chciałaś C# jesteś kompilacji używać niektórych klas itp w kodzie że jest to emitujące:

 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; 

w moim przypadku było emitujących klasę, którego nazwa została zapisana w ciąg, className, który miał jedną metodę statyczną o nazwie Get() publicznego, który wrócił z rodzaju StoryDataIds. Oto co wywołanie tej metody wygląda następująco:

 var tempType = asm.GetType(className); 
     var ids = (StoryDataIds)tempType.GetMethod("Get").Invoke(null, null); 
Powiązane problemy