সি # কোডের টুকরোগুলি গতিশীলভাবে সংকলন ও সম্পাদন করা সম্ভব?


177

আমি ভাবছিলাম যে কোনও টেক্সট ফাইলে (অথবা কোনও ইনপুট স্ট্রিম) সি # কোড টুকরা সংরক্ষণ করা সম্ভব, এবং তারপরে এইগুলি গতিশীলভাবে কার্যকর করা সম্ভব? আমাকে যা সরবরাহ করা হয় তা ধরে নিলে যে কোনও মেইন () ব্লকের মধ্যে জরিমানা সংকলন হবে, এই কোডটি সংকলন এবং / অথবা সম্পাদন করা সম্ভব? পারফরম্যান্সের কারণে আমি এটি সংকলন করতে পছন্দ করব।

খুব কমপক্ষে, আমি একটি ইন্টারফেসটি সংজ্ঞায়িত করতে পারি যে তাদের প্রয়োগ করার প্রয়োজন হবে, তারপরে তারা একটি কোড 'বিভাগ' সরবরাহ করবে যা সেই ইন্টারফেসটি কার্যকর করেছিল।


11
আমি জানি এই পোস্টটি কয়েক বছরের পুরনো, তবে আমি প্রকল্প রোজলিনের প্রবর্তনের সাথে এটি উল্লেখযোগ্য বলে মনে করেছি , ফ্লাইটে কাঁচা সি # সংকলন করতে এবং এটি একটি নেট প্রোগ্রামের মধ্যে চালানোর দক্ষতাটি একটু সামান্য সহজ।
লরেন্স

উত্তর:


176

সি # / সমস্ত স্থিতিশীল .NET ভাষার সর্বোত্তম সমাধান হ'ল এই জাতীয় জিনিসের জন্য কোডডম ব্যবহার করা। (একটি দ্রষ্টব্য হিসাবে, এর অন্যান্য মূল উদ্দেশ্যটি গতিময়ভাবে কোডের বিটগুলি এমনকি পুরো ক্লাসগুলি তৈরি করা))

এখানে লুক এইচ এর ব্লগ থেকে নেওয়া একটি ছোট সংক্ষিপ্ত উদাহরণ , যা মজাদার জন্য কিছু লিনকিউ ব্যবহার করে।

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ফ্লাইয়ের কোড সংকলনের জন্য সংকলকটি ব্যবহার করে। আপনি যদি কোডটি চালাতে চান তবে অ্যাসেম্বলিটিকে গতিশীলভাবে লোড করতে এবং এটি সম্পাদন করতে আপনাকে কিছুটা প্রতিচ্ছবি ব্যবহার করতে হবে।

এখানে সি # তে আরেকটি উদাহরণ রয়েছে যা (সামান্য কম সংক্ষিপ্ত হলেও) আপনাকে System.Reflectionনামের স্থানটি ব্যবহার করে রানটাইম-সংকলিত কোডটি কীভাবে চালাতে হবে তা সুনির্দিষ্টভাবে দেখায় ।


3
যদিও আমি আপনার মনোকে ব্যবহার করার বিষয়ে সন্দেহ করি, তবুও আমি ভেবেছিলাম এটি সার্থক হতে পারে যে এখানে মনো ( সিএসআরপি ) নামের স্থানটি রয়েছে ( মোনো-প্রজেক্ট / সিএসআরপি_কম্পিলার ) যা আসলে একটি পরিষেবা হিসাবে একটি সংকলক ধারণ করে যাতে আপনি গতিশীলভাবে মৌলিক কোড চালনা / মূল্যায়ন করতে পারবেন সর্বনিম্ন ঝামেলা সহ ইনলাইন এক্সপ্রেশন।
নলডোরিন

1
এটি করার জন্য সত্যিকারের বিশ্বের কী দরকার। আমি সাধারণভাবে প্রোগ্রামিংয়ে বেশ সবুজ এবং আমার মনে হয় এটি দুর্দান্ত তবে আপনি কেন চাইবেন এমন কোনও কারণ আমি ভাবতে পারি না / এটি কার্যকর হবে। ধন্যবাদ যদি আপনি ব্যাখ্যা করতে পারেন।
ক্র্যাশ 893

1
@ ক্র্যাশ 893: ডিজাইনার অ্যাপ্লিকেশনগুলির যে কোনও ধরণের জন্য স্ক্রিপ্টিং সিস্টেম এটির ভাল ব্যবহার করতে পারে। অবশ্যই, আইরন পাইথন এলইউএর মতো বিকল্প রয়েছে তবে এটি অবশ্যই একটি। মনে রাখবেন যে প্লাগইন সিস্টেমটি সরাসরি কোড লোড করার পরিবর্তে ইন্টারফেসগুলি প্রকাশ করে এবং সংকলিত ডিএলএলগুলি লোড করার মাধ্যমে আরও উন্নত হবে।
নলডোরিন 14

আমি সবসময় "কোডডম "টিকে এমন জিনিস হিসাবে ভেবেছিলাম যা আমাকে একটি ডম - একটি ডকুমেন্ট অবজেক্ট মডেল ব্যবহার করে একটি কোড ফাইল তৈরি করতে দেয়। System.CodeDom এ কোড সহ অন্তর্ভুক্ত সমস্ত শিল্পকর্মের প্রতিনিধিত্ব করার জন্য অবজেক্ট রয়েছে - শ্রেণীর জন্য একটি উপাদান, একটি ইন্টারফেসের জন্য, একজন নির্মাতা, বিবৃতি, সম্পত্তি, ক্ষেত্র ইত্যাদি and আমি তখন সেই অবজেক্ট মডেলটি ব্যবহার করে কোডটি তৈরি করতে পারি। এই উত্তরে এখানে যা দেখানো হয়েছে তা একটি প্রোগ্রামে একটি কোড ফাইল সংকলন করছে। কোডডম নয়, যদিও কোডডমের মতো এটি গতিশীলভাবে একটি অ্যাসেমব্লি তৈরি করে। সাদৃশ্য: আমি ডিওএম ব্যবহার করে বা স্ট্রিং কনক্যাট ব্যবহার করে একটি HTML পৃষ্ঠা তৈরি করতে পারি।
চিজো

: এখানে একটি তাই নিবন্ধটি যে শো ক্রিয়াটি CodeDom এর stackoverflow.com/questions/865052/...
Cheeso

61

আপনি মেমরিতে কোডের একটি টুকরো সি # সংকলন করতে পারেন এবং রোজলিনের সাথে অ্যাসেমব্লিং বাইট তৈরি করতে পারেন । এটি ইতিমধ্যে উল্লেখ করা হয়েছে তবে এর জন্য এখানে কিছু রোজলিন উদাহরণ যুক্ত করা উপযুক্ত। নিম্নলিখিত সম্পূর্ণ উদাহরণ:

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

এটি একই কোড যা সি # সংকলক ব্যবহার করে যা সবচেয়ে বেশি সুবিধা। কমপ্লেক্স একটি আপেক্ষিক শব্দ তবে রানটাইমটিতে সংকলন কোড যাইহোক করা একটি জটিল কাজ। তবে উপরের কোডটি মোটেই জটিল নয়।
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);

সতর্কতা: সংকলন আশ্চর্যজনকভাবে, অত্যন্ত ধীর হতে পারে। আমাদের তুলনামূলকভাবে দ্রুত সার্ভারে 2-10 সেকেন্ডের মধ্যে একটি প্রাথমিক, তুলনামূলকভাবে সহজ 10-লাইন কোড সাধারণ অগ্রাধিকারে সংকলন করে। আপনার কাছে কখনও কল করা উচিত নয়CompileAssemblyFromSource()ওয়েব অনুরোধের মতো আপনার স্বাভাবিক পারফরম্যান্স প্রত্যাশার সাথে কোনও কিছুর । পরিবর্তে, নিম্ন-অগ্রাধিকারের থ্রেডে আপনার প্রয়োজনীয় কোডটি সক্রিয়ভাবে সংকলন করতে হবে এবং কোডটি প্রস্তুত করার জন্য এমন একটি কোডের সাথে যোগাযোগ করার উপায় রয়েছে যাতে এটি সংকলন শেষ করার সুযোগ না পাওয়া পর্যন্ত code উদাহরণস্বরূপ, আপনি এটি ব্যাচের কাজের প্রক্রিয়াতে ব্যবহার করতে পারেন।


আপনার উত্তর অনন্য। অন্যরা আমার সমস্যা সমাধান করে না।
করুনআউটস্লাইম

3

সংকলন করতে আপনি কেবল সিসিএস সংকলকটিতে শেল কল শুরু করতে পারেন। আপনার পাথ এবং সুইচগুলি সোজা রাখার চেষ্টা করে আপনার মাথা ব্যথা হতে পারে তবে এটি অবশ্যই করা যেতে পারে।

সি # কর্নার শেল উদাহরণ

সম্পাদনা : বা আরও ভাল, নলডোরিনের পরামর্শ অনুযায়ী কোডডম ব্যবহার করুন ...


হ্যাঁ, কোডডোমের সাথে দুর্দান্ত জিনিসটি হ'ল এটি মেমরির ক্ষেত্রে আপনার পক্ষে সমাবেশ তৈরি করতে পারে (পাশাপাশি সহজেই পঠনযোগ্য ফর্ম্যাটে ত্রুটি বার্তা এবং অন্যান্য তথ্য সরবরাহ করে)।
নলডোরিন

3
@ নলডোরিন, সি # কোডডোম বাস্তবায়ন মেমরিতে আসলে কোনও সমাবেশ তৈরি করে না। আপনি এর জন্য পতাকাটি সক্ষম করতে পারেন তবে তা উপেক্ষা করা যায়। পরিবর্তে এটি একটি অস্থায়ী ফাইল ব্যবহার করে।
ম্যাথু ওলানিক

@ ম্যাট: হ্যাঁ, ভাল কথা - আমি সেই সত্যটি ভুলে গেছি। তবুও, এটি এখনও প্রক্রিয়াটিকে ব্যাপকভাবে সরল করে তোলে (এটি কার্যকরভাবে মেমরিরূপে অ্যাসেম্বলি তৈরি করা হয়েছে এমনভাবে তৈরি করে), এবং একটি সম্পূর্ণ পরিচালিত ইন্টারফেস সরবরাহ করে, যা প্রক্রিয়াগুলির সাথে ডিল করার চেয়ে অনেক সুন্দর।
নলডোরিন

এছাড়াও, কোডডমপ্রোভাইডার হ'ল একটি শ্রেণি যা যাইহোক csc.exe এ কল করে।
justin.m.chase hase

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"); }
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.