Можно ли динамически компилировать и выполнять фрагменты кода C #? - PullRequest
159 голосов
/ 05 мая 2009

Мне было интересно, можно ли сохранить фрагменты кода C # в текстовый файл (или любой поток ввода), а затем выполнить их динамически? Предполагая, что то, что предоставлено мне, будет нормально компилироваться в любом блоке Main (), возможно ли скомпилировать и / или выполнить этот код? Я бы предпочел скомпилировать его из соображений производительности.

По крайней мере, я мог бы определить интерфейс, который они должны были бы реализовать, затем они предоставили бы кодовый раздел, который реализовывал этот интерфейс.

Ответы [ 5 ]

162 голосов
/ 05 мая 2009

Лучшее решение в C # / всех статических .NET языках - это использовать CodeDOM для таких вещей. (Как примечание, его другое основное назначение - динамическое конструирование битов кода или даже целых классов.)

Вот хороший короткий пример, взятый из блога LukeH , в котором также используется 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.

50 голосов
/ 02 апреля 2015

Вы можете скомпилировать кусок C # кода в память и сгенерировать ассемблерные байты с Roslyn. Это уже упоминалось, но здесь стоит добавить пример 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)
        {
            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();
        }
    }
}
38 голосов
/ 05 мая 2009

Другие уже дали хорошие ответы о том, как генерировать код во время выполнения, поэтому я подумал, что расскажу о вашем втором абзаце. У меня есть некоторый опыт в этом, и я просто хочу поделиться уроком, который я извлек из этого опыта.

По крайней мере, я мог бы определить интерфейс, который им потребуется реализовать, то они предоставят «раздел» кода, который реализовал это интерфейс.

У вас могут быть проблемы, если вы используете interface в качестве базового типа. Если в будущем вы добавите один новый метод к interface, все существующие клиентские классы, которые реализуют interface, теперь станут абстрактными, то есть вы не сможете скомпилировать или создать экземпляр клиентского класса во время выполнения.

У меня была эта проблема, когда пришло время добавить новый метод примерно через 1 год после доставки старого интерфейса и после распространения большого количества «устаревших» данных, которые необходимо было поддерживать. Я закончил тем, что создал новый интерфейс, унаследованный от старого, но этот подход усложнил загрузку и создание экземпляров клиентских классов, потому что я должен был проверить, какой интерфейс был доступен.

Одним из решений, о котором я думал в то время, было вместо этого использовать фактический класс в качестве базового типа, такого как приведенный ниже. Сам класс может быть помечен как абстрактный, но все методы должны быть пустыми виртуальными методами (не абстрактными методами). Затем клиенты могут переопределить нужные им методы, и я могу добавить новые методы в базовый класс, не аннулируя существующий предоставленный клиентом код.

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

Независимо от того, применяется ли эта проблема, вам следует подумать о том, как создать версию интерфейса между вашей кодовой базой и предоставленным клиентом кодом.

3 голосов
/ 05 мая 2009

Для компиляции вы можете просто инициировать вызов оболочки для компилятора csc. Вы можете испытывать головную боль, пытаясь удержать ваши пути и переключатели прямо, но это, безусловно, можно сделать.

Примеры C # угловой оболочки

РЕДАКТИРОВАТЬ : Или еще лучше, используйте CodeDOM, как предложил Нолдорин ...

1 голос
/ 09 декабря 2017

Нашел это полезным - гарантирует, что скомпилированная сборка ссылается на все, на что вы ссылались в данный момент, поскольку есть большая вероятность, что вы хотите, чтобы компилируемый C # использовал некоторые классы и т. Д. В коде, который выдает это:

        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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...