क्या C # कोड अंशों को गतिशील रूप से संकलित और निष्पादित करना संभव है?


177

मैं सोच रहा था कि क्या किसी पाठ फ़ाइल (या किसी इनपुट स्ट्रीम) में C # कोड अंशों को सहेजना संभव है, और फिर उन गतिशील रूप से निष्पादित करें? यह मानते हुए कि मुझे जो प्रदान किया गया है, वह किसी भी मुख्य () ब्लॉक के भीतर ठीक संकलित करेगा, क्या इस कोड को संकलित करना और / या निष्पादित करना संभव है? मैं प्रदर्शन कारणों से इसे संकलित करना पसंद करूंगा।

बहुत कम से कम, मैं एक इंटरफ़ेस परिभाषित कर सकता हूं जिसे उन्हें लागू करने की आवश्यकता होगी, फिर वे एक कोड 'अनुभाग' प्रदान करेंगे जो उस इंटरफ़ेस को लागू करता है।


11
मुझे पता है कि यह पोस्ट कुछ साल पुरानी है, लेकिन मुझे लगा कि प्रोजेक्ट रोसलिन की शुरुआत के साथ ही यह ध्यान देने लायक है कि रॉ सी # को उड़ने और इसे .NET प्रोग्राम के भीतर चलाने की क्षमता बस थोड़ी आसान है।
लॉरेंस

जवाबों:


176

C # / सभी स्थिर .NET भाषाओं में सबसे अच्छा समाधान ऐसी चीजों के लिए कोडडॉम का उपयोग करना है । (एक नोट के रूप में, इसका अन्य मुख्य उद्देश्य गतिशील रूप से कोड के बिट्स का निर्माण करना है, या पूरे वर्ग के लिए भी है।)

यहाँ एक छोटा छोटा उदाहरण ल्यूक के ब्लॉग से लिया गया है , जो कुछ LINQ का उपयोग सिर्फ मनोरंजन के लिए करता है।

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

यहां प्राथमिक महत्व का वर्ग वह है CSharpCodeProviderजो मक्खी पर कोड संकलित करने के लिए संकलक का उपयोग करता है। यदि आप कोड को चलाना चाहते हैं, तो आपको बस विधानसभा को गतिशील रूप से लोड करने और इसे निष्पादित करने के लिए थोड़ा सा प्रतिबिंब का उपयोग करने की आवश्यकता है।

यहाँ C # में एक और उदाहरण दिया गया है (हालाँकि थोड़ा कम संक्षिप्त रूप) इसके अतिरिक्त आपको ठीक-ठीक पता चलता है कि System.Reflectionनेमस्पेस का उपयोग करके रनटाइम-संकलित कोड कैसे चलाया जाता है ।


3
हालाँकि मुझे आपके मोनो का उपयोग करने पर संदेह है, मैंने सोचा कि यह इंगित करने योग्य हो सकता है कि एक Mono.CSharp नाम स्थान ( मोनो-project.com/CSharp_Compiler ) मौजूद है जिसमें वास्तव में एक सेवा के रूप में एक संकलक शामिल है ताकि आप बुनियादी कोड / मूल्यांकन का गतिशील रूप से मूल्यांकन कर सकें कम से कम परेशानी के साथ अभिव्यक्ति इनलाइन।
नोल्डोरिन

1
ऐसा करने के लिए एक वास्तविक दुनिया की आवश्यकता क्या होगी। मैं सामान्य रूप से प्रोग्रामिंग में बहुत हरा हूं और मुझे लगता है कि यह अच्छा है लेकिन मैं एक कारण नहीं सोच सकता कि आप क्यों चाहते हैं / यह उपयोगी होगा। धन्यवाद अगर आप समझा सकते हैं।
क्रैश89 3

1
@ Crash893: किसी भी तरह के डिजाइनर एप्लिकेशन के लिए एक स्क्रिप्टिंग प्रणाली इस का अच्छा उपयोग कर सकती है। बेशक, आयरनप्यटन एलयूए जैसे विकल्प हैं, लेकिन यह निश्चित रूप से एक है। ध्यान दें कि एक प्लगइन सिस्टम सीधे इंटरफेस लोड करने के बजाय इंटरफेस और लोडिंग संकलित DLL को बेहतर तरीके से विकसित किया जाएगा।
नोल्डोरिन

मैं हमेशा "कोडडोम" के बारे में सोचता था जो मुझे डोम - डॉक्यूमेंट ऑब्जेक्ट मॉडल का उपयोग करके कोड फ़ाइल बनाने देता है। System.CodeDom में, उन सभी कलाकृतियों का प्रतिनिधित्व करने के लिए ऑब्जेक्ट हैं जिनमें कोड शामिल है - एक क्लास के लिए एक ऑब्जेक्ट, एक इंटरफ़ेस के लिए, एक कंस्ट्रक्टर के लिए, एक स्टेटमेंट, एक प्रॉपर्टी, एक फील्ड, और इसी तरह। मैं उस ऑब्जेक्ट मॉडल का उपयोग करके कोड का निर्माण कर सकता हूं। इस उत्तर में जो दिखाया गया है, वह एक प्रोग्राम में कोड फ़ाइल संकलित कर रहा है। कोडडॉम नहीं, हालांकि कोडडॉम की तरह, यह गतिशील रूप से एक विधानसभा का उत्पादन करता है। सादृश्य: मैं DOM का उपयोग करके, या स्ट्रिंग समवर्ती का उपयोग करके एक HTML पृष्ठ बना सकता हूँ।
चीजो

यहाँ एक SO लेख है जो कोडडॉम को कार्रवाई में दिखाता है: stackoverflow.com/questions/865052/…
चीज़ो

61

आप कोड का एक टुकड़ा C # मेमोरी में संकलित कर सकते हैं और Roslyn के साथ असेंबली बाइट्स उत्पन्न कर सकते हैं । यह पहले से ही उल्लेख किया गया है, लेकिन यहां इसके लिए कुछ रोसलिन उदाहरण जोड़ने लायक होगा। निम्नलिखित पूरा उदाहरण है:

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 # कंपाइलर उपयोग करता है जो सबसे बड़ा लाभ है। कॉम्प्लेक्स एक सापेक्ष शब्द है, लेकिन रनटाइम पर कोड संकलित करना वैसे भी एक जटिल काम है। हालाँकि, उपरोक्त कोड जटिल नहीं है।
tugberk

41

दूसरों ने पहले ही अच्छे उत्तर दे दिए हैं कि रनटाइम पर कोड कैसे उत्पन्न किया जाए तो मैंने सोचा कि मैं आपके दूसरे पैराग्राफ को संबोधित करूंगा। मुझे इसके साथ कुछ अनुभव है और मैं उस अनुभव से सीखा हुआ एक सबक साझा करना चाहता हूं।

बहुत कम से कम, मैं एक इंटरफ़ेस परिभाषित कर सकता हूं जिसे उन्हें लागू करने की आवश्यकता होगी, फिर वे एक कोड 'अनुभाग' प्रदान करेंगे जो उस इंटरफ़ेस को लागू करता है।

यदि आप interfaceआधार प्रकार के रूप में उपयोग करते हैं तो आपको एक समस्या हो सकती है । यदि आप interfaceभविष्य में सभी मौजूदा ग्राहक-आपूर्ति वाली कक्षाओं में एक नई विधि जोड़ते interfaceहैं, जो अब लागू हो जाती है, जिसका अर्थ है कि आप रन-टाइम पर ग्राहक-आपूर्ति वर्ग को संकलित या त्वरित करने में सक्षम नहीं होंगे।

मेरे पास यह मुद्दा था जब पुराने इंटरफ़ेस को शिपिंग करने के लगभग 1 साल बाद एक नई विधि को जोड़ने का समय आया और बड़ी मात्रा में "विरासत" डेटा वितरित करने के बाद जिसे समर्थन करने की आवश्यकता थी। मैंने एक नया इंटरफ़ेस बनाना शुरू कर दिया है जो पुराने से विरासत में मिला है, लेकिन इस दृष्टिकोण ने क्लाइंट-सप्लाई की गई कक्षाओं को लोड करना और उन्हें इंस्टेंट करना कठिन बना दिया है क्योंकि मुझे यह जांचना था कि कौन सा इंटरफ़ेस उपलब्ध था।

उस समय मैंने जो एक समाधान सोचा था, वह आधार के रूप में एक वास्तविक वर्ग का उपयोग करना था जैसे नीचे दिया गया। कक्षा को ही सार के रूप में चिह्नित किया जा सकता है, लेकिन सभी तरीकों को खाली आभासी तरीके (सार तरीके नहीं) होने चाहिए। ग्राहक तब अपने इच्छित तरीकों को ओवरराइड कर सकते हैं और मैं मौजूदा क्लायंट-सप्लाई कोड को अमान्य किए बिना बेस क्लास में नए तरीके जोड़ सकते हैं।

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

भले ही यह समस्या लागू हो, आपको इस पर विचार करना चाहिए कि अपने कोड बेस और क्लाइंट-सप्लाइ किए गए कोड के बीच इंटरफ़ेस कैसे करें।


2
यह एक मूल्यवान, सहायक परिप्रेक्ष्य है।
चीजो

5

यह उपयोगी पाया गया - संकलित असेंबली संदर्भों को सुनिश्चित करता है जो आपने वर्तमान में संदर्भित किया है, क्योंकि एक अच्छा मौका है जो आप चाहते थे कि सी # आप कुछ कक्षाओं आदि का उपयोग करने के लिए संकलित कर रहे हैं जो इसे उत्सर्जित कर रहे हैं:

        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;

मेरे मामले में, मैं एक वर्ग का उत्सर्जन कर रहा था, जिसका नाम एक स्ट्रिंग में संग्रहीत किया गया था className, जिसमें एक एकल सार्वजनिक स्थैतिक विधि थी, जिसका नाम Get()टाइप के साथ वापस आ गया था StoryDataIds। यहां बताया गया है कि कॉलिंग विधि क्या है:

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

चेतावनी: संकलन आश्चर्यजनक रूप से, अत्यंत धीमा हो सकता है। कोड का एक छोटा, अपेक्षाकृत सरल 10-लाइन वाला हिस्सा हमारे अपेक्षाकृत तेज सर्वर पर 2-10 सेकंड में सामान्य प्राथमिकता पर संकलन करता है। आपको CompileAssemblyFromSource()वेब अनुरोध की तरह सामान्य प्रदर्शन अपेक्षाओं के साथ कॉल को कभी भी टाई नहीं करना चाहिए । इसके बजाय, संकलित कोड को आपको कम-प्राथमिकता वाले धागे की आवश्यकता होती है और कोड से निपटने का एक तरीका होता है जिसके लिए उस कोड को तैयार होने की आवश्यकता होती है, जब तक कि उसे संकलन समाप्त करने का मौका न हो। उदाहरण के लिए आप एक बैच नौकरी प्रक्रिया में इसका इस्तेमाल कर सकते हैं।


आपका उत्तर अद्वितीय है। दूसरे मेरी समस्या का समाधान नहीं करते हैं।
FindOutIslamNow

3

संकलित करने के लिए आप केवल csc संकलक के लिए एक शेल कॉल आरंभ कर सकते हैं। आपको अपने मार्ग और स्विच को सीधा रखने की कोशिश करने में सिरदर्द हो सकता है लेकिन यह निश्चित रूप से किया जा सकता है।

सी # कॉर्नर शैल उदाहरण

संपादित करें : या बेहतर अभी तक, कोडरॉम का उपयोग करें जैसा कि नोल्डोरिन ने सुझाव दिया ...


हाँ, कोडडॉम के साथ अच्छी बात यह है कि यह मेमोरी में आपके लिए असेंबली (साथ ही आसानी से पढ़ने योग्य प्रारूप में त्रुटि संदेश और अन्य जानकारी प्रदान करने के लिए) उत्पन्न कर सकता है।
नोल्डोरिन

3
@ नोल्डोरिन, सी # कोडडोम कार्यान्वयन वास्तव में स्मृति में एक विधानसभा उत्पन्न नहीं करता है। आप इसके लिए ध्वज को सक्षम कर सकते हैं, लेकिन इसे अनदेखा कर दिया जाता है। इसके बजाय एक अस्थायी फ़ाइल का उपयोग करता है।
मैथ्यू ओलेनिक

@ मैट: हाँ, अच्छी बात है - मैं उस तथ्य को भूल गया। फिर भी, यह अभी भी प्रक्रिया को बहुत सरल करता है (यह प्रभावी रूप से प्रकट होता है जैसे कि विधानसभा स्मृति में उत्पन्न हुई थी), और एक पूर्ण प्रबंधित इंटरफ़ेस प्रदान करता है, जो प्रक्रियाओं से निपटने की तुलना में बहुत अच्छा है।
नोल्डोरिन

इसके अलावा, कोडडोमप्रॉइडर सिर्फ एक वर्ग है जो वैसे भी csc.exe में कॉल करता है।
justin.m.chase

1

मुझे हाल ही में यूनिट टेस्टिंग के लिए प्रक्रियाओं की आवश्यकता थी। यह पोस्ट उपयोगी थी क्योंकि मैंने अपनी परियोजना से एक स्ट्रिंग या कोड के रूप में कोड के साथ ऐसा करने के लिए एक साधारण वर्ग बनाया। इस वर्ग का निर्माण करने के लिए, आपको ICSharpCode.Decompilerऔर Microsoft.CodeAnalysisNuGet पैकेज की आवश्यकता होगी । यहाँ वर्ग है:

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

इसका उपयोग करने के लिए, Runनीचे दिए गए तरीकों को कॉल करें :

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"); }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.