C # kod parçalarını dinamik olarak derlemek ve yürütmek mümkün mü?


177

Bir metin dosyasına (veya herhangi bir giriş akışı) C # kod parçaları kaydetmek ve sonra bu dinamik yürütmek mümkün olup olmadığını merak ediyordum? Bana verilenin herhangi bir Main () bloğu içinde iyi derlendiğini varsayarsak, bu kodu derlemek ve / veya çalıştırmak mümkün müdür? Performans nedenleriyle derlemeyi tercih ederim.

En azından, uygulanması gereken bir arabirim tanımlayabilirim, o zaman bu arabirimi uygulayan bir kod 'bölümü' sağlarlardı.


11
Bu yazının birkaç yaşında olduğunu biliyorum, ancak Project Roslyn'in tanıtımı ile bahsetmeye değer olduğunu düşündüm , ham C #'ı derleme ve bir .NET programı içinde çalıştırma yeteneği biraz daha kolay.
Lawrence

Yanıtlar:


176

C # / tüm statik .NET dillerinde en iyi çözüm böyle şeyler için CodeDOM kullanmaktır . (Not olarak, diğer ana amacı kod parçalarını ve hatta tüm sınıfları dinamik olarak oluşturmaktır.)

İşte bazı eğlenceli LINQ'ları kullanan LukeH blogundan kısa bir örnek .

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

Burada birincil öneme sahip olan sınıf CSharpCodeProviderderleyiciyi kodu derlemede kullanan sınıftır . Daha sonra kodu çalıştırmak istiyorsanız, derlemeyi dinamik olarak yüklemek ve yürütmek için biraz yansıma kullanmanız yeterlidir.

İşte C # 'da (biraz daha az özlü olmasına rağmen) ek olarak, System.Reflectionad alanını kullanarak çalışma zamanı derlenmiş kodunu nasıl çalıştıracağınızı gösteren başka bir örnek .


3
Mono kullandığınızdan şüphe duysam da , temel kod / değerlendirmeyi dinamik olarak çalıştırabilmeniz için aslında bir derleyici hizmet olarak bir Mono.CSharp ad alanı ( mono-project.com/CSharp_Compiler ) olduğunu belirtmek faydalı olabilir. ifadeler satır içi, minimum güçlükle.
Noldorin

1
bunu yapmak için gerçek bir dünyanın ihtiyacı olan şey. Genel olarak programlamada oldukça yeşilim ve bunun havalı olduğunu düşünüyorum ama neden isteyeceğinizin bir sebebini düşünemiyorum / bu yararlı olacaktır. Açıklayabilirsen teşekkürler.
Çarpışma893

1
@ Crash893: Hemen hemen her türlü tasarımcı uygulaması için bir betik sistemi bunu iyi kullanabilir. Tabii ki, IronPython LUA gibi alternatifler var, ama bu kesinlikle bir tane. Bir eklenti sisteminin, doğrudan kod yüklemek yerine, arayüzleri açığa çıkararak ve bunların uygulamalarını içeren derlenmiş DLL'leri yükleyerek daha iyi geliştirilebileceğini unutmayın.
Noldorin

Ben her zaman "CodeDom" DOM ​​- bir belge nesne modeli kullanarak bir kod dosyası oluşturmama izin şey olarak düşündüm. System.CodeDom'da, kodun içerdiği tüm yapay nesneleri temsil eden nesneler vardır - bir sınıf, bir arabirim, bir yapıcı, bir ifade, bir özellik, bir alan ve benzeri için bir nesne. Daha sonra bu nesne modelini kullanarak kod oluşturabilirim. Burada bu cevapta gösterilen, bir programda bir kod dosyası derlemektir. CodeDom değil, CodeDom gibi dinamik olarak bir montaj üretir. Benzetme: DOM kullanarak veya dize kesişme kullanarak bir HTML sayfası oluşturabilirim.
Cheeso


61

Bir parça C # kodunu belleğe derleyebilir ve Roslyn ile montaj baytları oluşturabilirsiniz . Zaten bahsedildi, ancak burada bunun için bazı Roslyn örneği eklemeye değer. Tam örnek aşağıdadır:

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)
        {
            // define source code, then parse it (to the type used for compilation)
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
                using System;

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

            // define other necessary objects for compilation
            string assemblyName = Path.GetRandomFileName();
            MetadataReference[] references = new MetadataReference[]
            {
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
            };

            // analyse and generate IL code from syntax tree
            CSharpCompilation compilation = CSharpCompilation.Create(
                assemblyName,
                syntaxTrees: new[] { syntaxTree },
                references: references,
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            {
                // write IL code into memory
                EmitResult result = compilation.Emit(ms);

                if (!result.Success)
                {
                    // handle exceptions
                    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
                {
                    // load this 'virtual' DLL so that we can use
                    ms.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(ms.ToArray());

                    // create instance of the desired class and call the desired function
                    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();
        }
    }
}

C # derleyicisinin kullandığı kodla aynıdır. Karmaşık göreceli bir terimdir, ancak çalışma zamanında kod derleme yine de yapmak karmaşık bir iştir. Ancak, yukarıdaki kod hiç karmaşık değildir.
tugberk

41

Diğerleri zaten çalışma zamanında kod üretme konusunda iyi cevaplar verdim, bu yüzden ikinci paragrafınızı ele alacağımı düşündüm. Bununla ilgili biraz deneyimim var ve sadece bu deneyimden öğrendiğim bir dersi paylaşmak istiyorum.

En azından, uygulanması gereken bir arabirim tanımlayabilirim, o zaman bu arabirimi uygulayan bir kod 'bölümü' sağlarlardı.

interfaceTemel tür olarak bir kullanırsanız sorun yaşayabilirsiniz . Gelecekte tek bir yeni yöntem eklerseniz interface, interfaceşimdi uygulayan mevcut tüm istemci tarafından sağlanan sınıflar soyut hale gelir, yani istemci tarafından sağlanan sınıfı çalışma zamanında derleyemez veya başlatamazsınız.

Eski arayüzü göndermeden yaklaşık 1 yıl sonra ve desteklenmesi gereken çok sayıda "eski" veri dağıttıktan sonra yeni bir yöntem ekleme zamanı geldiğinde bu sorunu yaşadım. Eskisinden miras kalan yeni bir arayüz oluşturdum ama bu yaklaşım, hangi arayüzün mevcut olduğunu kontrol etmek zorunda kaldım çünkü müşteri tarafından sağlanan sınıfları yüklemeyi ve başlatmayı zorlaştırdı.

O zaman düşündüğüm bir çözüm, bunun yerine gerçek bir sınıfı aşağıdaki gibi bir temel tür olarak kullanmaktı. Sınıfın kendisi soyut olarak işaretlenebilir, ancak tüm yöntemler boş sanal yöntemler olmalıdır (soyut yöntemler değil). İstemciler daha sonra istedikleri yöntemleri geçersiz kılabilir ve mevcut istemci tarafından sağlanan kodu geçersiz kılmadan temel sınıfa yeni yöntemler ekleyebilirim.

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

Bu sorunun geçerli olup olmadığına bakılmaksızın, kod tabanınızla istemci tarafından sağlanan kod arasındaki arabirimin nasıl sürümlendirileceğini düşünmelisiniz.


2
bu değerli ve yardımcı bir bakış açısıdır.
Cheeso

5

Bu yararlı bulundu - derlediğiniz Meclis referans şu anda her şeyi referans sağlar, çünkü derleyen C # derleyen bazı sınıflar vb bu yayan kod kullanmak için iyi bir şans var:

        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;

Benim durumumda Adını bir dize, saklanan bir sınıf yaydığını classNametek public static yöntem adında vardı Get()türüyle döndüğünü, StoryDataIds. Bu yöntemi çağırmak şöyle görünür:

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

Uyarı: Derleme şaşırtıcı derecede son derece yavaş olabilir. Nispeten hızlı sunucumuzda küçük, nispeten basit 10 satırlık bir kod normal öncelikte 2-10 saniye içinde derlenir. CompileAssemblyFromSource()Web isteği gibi normal performans beklentileri olan hiçbir şeye asla çağrı yapmamalısınız . Bunun yerine, düşük öncelikli bir iş parçacığında ihtiyacınız olan kodu proaktif olarak derleyin ve derleme işlemini tamamlama şansı elde edene kadar bu kodun hazır olmasını gerektiren kodla başa çıkmanın bir yoluna sahip olun. Örneğin, bir toplu iş işleminde kullanabilirsiniz.


Cevabınız benzersiz. Diğerleri sorunumu çözmez.
FindOutIslamNow

3

Derlemek için csc derleyicisine bir kabuk çağrısı başlatabilirsiniz. Yollarınızı ve anahtarlarınızı düz tutmaya çalışan bir baş ağrınız olabilir, ancak kesinlikle yapılabilir.

C # Köşe Kabuğu Örnekleri

DÜZENLEME : Ya da daha iyisi, CodeDOM'u Noldorin'in önerdiği gibi kullanın ...


Evet, CodeDOM'un güzel yanı, montajı bellekte sizin için oluşturabilmesidir (hata mesajlarını ve diğer bilgileri kolayca okunabilir bir formatta sağlamanın yanı sıra).
Noldorin

3
@Noldorin, C # CodeDOM uygulaması aslında bellekte bir derleme oluşturmaz. Bayrağı bunun için etkinleştirebilirsiniz, ancak yok sayılır. Bunun yerine geçici bir dosya kullanır.
Matthew Olenik

@ Matt: Evet, iyi bir nokta - Bu gerçeği unuttum. Bununla birlikte, süreci büyük ölçüde basitleştirir ( montajın bellekte oluşturulmuş gibi etkili bir şekilde görünmesini sağlar ) ve süreçlerle uğraşmaktan çok daha iyi olan eksiksiz bir yönetilen arayüz sunar.
Noldorin

Ayrıca, CodeDomProvider zaten csc.exe çağıran bir sınıftır.
justin.m.chase

1

Geçenlerde birim testi için süreçler üretmem gerekiyordu. Bu yazı, projemden bir dize veya kod olarak kod ile yapmak için basit bir sınıf oluştururken yararlı oldu. Bu sınıfı oluşturmak için ICSharpCode.Decompilerve Microsoft.CodeAnalysisNuGet paketlerine ihtiyacınız olacak . İşte sınıf:

using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

public static class CSharpRunner
{
   public static object Run(string snippet, IEnumerable<Assembly> references, string typeName, string methodName, params object[] args) =>
      Invoke(Compile(Parse(snippet), references), typeName, methodName, args);

   public static object Run(MethodInfo methodInfo, params object[] args)
   {
      var refs = methodInfo.DeclaringType.Assembly.GetReferencedAssemblies().Select(n => Assembly.Load(n));
      return Invoke(Compile(Decompile(methodInfo), refs), methodInfo.DeclaringType.FullName, methodInfo.Name, args);
   }

   private static Assembly Compile(SyntaxTree syntaxTree, IEnumerable<Assembly> references = null)
   {
      if (references is null) references = new[] { typeof(object).Assembly, typeof(Enumerable).Assembly };
      var mrefs = references.Select(a => MetadataReference.CreateFromFile(a.Location));
      var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), new[] { syntaxTree }, mrefs, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

      using (var ms = new MemoryStream())
      {
         var result = compilation.Emit(ms);
         if (result.Success)
         {
            ms.Seek(0, SeekOrigin.Begin);
            return Assembly.Load(ms.ToArray());
         }
         else
         {
            throw new InvalidOperationException(string.Join("\n", result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error).Select(d => $"{d.Id}: {d.GetMessage()}")));
         }
      }
   }

   private static SyntaxTree Decompile(MethodInfo methodInfo)
   {
      var decompiler = new CSharpDecompiler(methodInfo.DeclaringType.Assembly.Location, new DecompilerSettings());
      var typeInfo = decompiler.TypeSystem.MainModule.Compilation.FindType(methodInfo.DeclaringType).GetDefinition();
      return Parse(decompiler.DecompileTypeAsString(typeInfo.FullTypeName));
   }

   private static object Invoke(Assembly assembly, string typeName, string methodName, object[] args)
   {
      var type = assembly.GetType(typeName);
      var obj = Activator.CreateInstance(type);
      return type.InvokeMember(methodName, BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, args);
   }

   private static SyntaxTree Parse(string snippet) => CSharpSyntaxTree.ParseText(snippet);
}

Kullanmak için Runaşağıdaki yöntemleri çağırın :

void Demo1()
{
   const string code = @"
   public class Runner
   {
      public void Run() { System.IO.File.AppendAllText(@""C:\Temp\NUnitTest.txt"", System.DateTime.Now.ToString(""o"") + ""\n""); }
   }";

   CSharpRunner.Run(code, null, "Runner", "Run");
}

void Demo2()
{
   CSharpRunner.Run(typeof(Runner).GetMethod("Run"));
}

public class Runner
{
   public void Run() { System.IO.File.AppendAllText(@"C:\Temp\NUnitTest.txt", System.DateTime.Now.ToString("o") + "\n"); }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.