Динамическая компиляция кода в C#

    Использовать компилятор из кода C# достаточно просто. А вот зачем – это другой вопрос :).

    Hello World


    Напишем первый простой пример. Создаем консольное приложение и напишем следующий код:
    using System;<br>using System.CodeDom.Compiler;<br>using System.Collections.Generic;<br>using Microsoft.CSharp;<br><br>namespace ConsoleCompiler<br>{<br>  internal class Program<br>  {<br>    private static void Main(string[] args)<br>    {<br>      // Source code для компиляции<br>      string source =<br>      @"<br>namespace Foo<br>{<br>  public class Bar<br>  {<br>    static void Main(string[] args)<br>    {<br>      Bar.SayHello();<br>    }<br><br>    public static void SayHello()<br>    {<br>      System.Console.WriteLine(""Hello World"");<br>    }<br>  }<br>}<br>      ";<br><br>      // Настройки компиляции<br>      Dictionary<string, string> providerOptions = new Dictionary<string, string><br>        {<br>          {"CompilerVersion", "v3.5"}<br>        };<br>      CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);<br><br>      CompilerParameters compilerParams = new CompilerParameters<br>        {OutputAssembly = "D:\\Foo.EXE", GenerateExecutable = true};<br><br>      // Компиляция<br>      CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);<br><br>      // Выводим информацию об ошибках<br>      Console.WriteLine("Number of Errors: {0}", results.Errors.Count);<br>      foreach (CompilerError err in results.Errors)<br>      {<br>        Console.WriteLine("ERROR {0}", err.ErrorText);<br>      }<br>    }<br>  }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

    Запускаем и проверяем:
    First Sample
    Первое, на что стоит обратить внимание – это использование двух пространств имен (namespace):
    • Microsoft.CSharp
    • System.CodeDom.Compiler
    В данных классах и содержится ключ к возможности компиляции. В нашем примере мы указываем что компилировать будем под .NET Framework 3.5, а так же указываем что мы хотим получить на выходе – Foo.exe, с возможностью запуска данного приложения.

    Пример посложнее, используем Linq


    Теперь давайте усложним наш пример, в компилируемый код добавим использование Linq:
    string source = @"<br>using System.Collections.Generic;<br>using System.Linq;<br><br>namespace Foo<br>{<br>  public class Bar<br>  {<br>    static void Main(string[] args)<br>    {<br>      Bar.SayHello();<br>    }<br><br>    public static void SayHello()<br>    {<br>      System.Console.WriteLine(""Hello World"");<br>      System.Console.WriteLine( string.Join("","", Enumerable.Range(0,10).Select(n=>n.ToString()).ToArray() ) );<br>    }<br>  }<br>}";<br><br>* This source code was highlighted with Source Code Highlighter.

    Добавленные строки помечены красным, если мы попробуем запустить предыдущий пример с измененным компилируемым кодом, то теперь мы увидим ошибки компиляции:
    Compiler Error
    Чтобы компиляция удалась, необходимо добавить в параметры компиляции ссылку на сборку System.Core.dll
    compilerParams.ReferencedAssemblies.Add("System.Core.Dll");<br><br>* This source code was highlighted with Source Code Highlighter.

    И теперь все будет работать:
    Linq Sample

    Используем созданную сборку в коде


    Теперь попробуем скомпилировать сборку Foo.dll вместо исполняемого файла, а так же сразу же после компиляции загрузить и использовать скомпилированный метод. Компилируемый код мы изменим, сделаем его попроще:
    stringsource = @"<br>using System.Collections.Generic;<br>using System.Linq;<br><br>namespace Foo<br>{<br>  public class Bar<br>  {<br>    public static void SayHello()<br>    {<br>      System.Console.WriteLine(""Hello World"");<br>      System.Console.WriteLine( string.Join("","", Enumerable.Range(0,10).Select(n=>n.ToString()).ToArray() ) );<br>    }<br>  }<br>}";<br><br>* This source code was highlighted with Source Code Highlighter.

    Изменим настройки компилятора, теперь будем собирать dll файл:
    const string outputAssembly = "D:\\Foo.dll";<br>CompilerParameters compilerParams = new CompilerParameters {OutputAssembly = outputAssembly, GenerateExecutable = false};<br><br>* This source code was highlighted with Source Code Highlighter.

    После компиляции и проверки ошибок используя Reflection (не забываем подключить пространство имен — using System.Reflection) вызовем метод Foo.Bar.SayHello() скомпилированной dll:
    Console.WriteLine("Try Assembly:");<br>Assembly assembly = Assembly.LoadFile(outputAssembly);<br>Type type = assembly.GetType("Foo.Bar");<br>MethodInfo method = type.GetMethod("SayHello");<br>method.Invoke(null, null);<br><br>* This source code was highlighted with Source Code Highlighter.

    Результат:
    Final Result
    Финальный пример можно скачать с mydrive.live.com.
    Информацию о динамической компиляции и основные примеры я взял отсюда: Saveen Reddy's blog — A Walkthrough of Dynamically Compiling C# code (Английский).
    Progg it
    Поделиться публикацией
    Комментарии 35
      +1
      Использую данныый метод для Eval javascript'а на стороне сервера, только сборку создаю в памяти
        +4
        Ну да, одна из штатных возможностей языка, так работает, например, XmlSerializer. Вкупе с будущим dynamic — простор открывается совершенно невообразимый.

        Только пост почему-то не о чем.
          +9
          Маленькое уточнение: отнимите Full Trust, и посмотрите, что получится — компилятор сейчас не является управляемым компонентном (он COM-объект), поэтому в отсутствие спец.привилегий скомпилировать удастся чуть менее, чем никак. В отличие от Boo или Nemerle, например. Так что если у вас чужой хостинг, будьте внимательны с permissions при развертывании такого решения — некоторые хостеры могут просто не дать Full Trust.

          А еще компилировать можно в память — если эта сборка больше никому не понадобится. А еще эмбеддить файлы внутрь сборки. А еще… Короче, ни о чем пост.
            +2
            Эхх, кросспостеры :( Там у вас перевод качественнее, с картинками; а сюда в текст попали alt-ы к картинкам (Final Result, Linq Sample). Вы что, вслепую копипастили?
              +1
              Не в слепую. Картинки должны быть.
                0
                Сорри, тогда заминусуйте :/
                  +1
                  да ну, я не злой :)
              +1
              А еще любой топик или текст можно чем то дополнить. И что дальше?

              Больше половины информации что я вижу в своих RSS (та информация, которая может меня заинтересовать в плане профессиональной направленности) — это та информация, о которой я уже знаю, но не забывайте что на планете не вы один, а несколько миллиардов человек, и некоторому проценту любая информация может быть полезна.

              Так что какие то не понятные «А еще...» у вас получается. Если хотите дополнить данный топик, так я только буду рад и с удовольствием прочту.

              Удачи!
                +1
                Пристыдили:)

                Я написал важное дополнение про Full Trust.
                Еще одно важное дополнение — начиная с .NET 3.5 SP 1 доступен .NET Client Profile — подмножество .NET FW; он относится к полноразмерному .net так же, как JRE относится к JDK. В настоящий момент туда включен компилятор csc, но пользоваться им нельзя (крайне не рекомендуется) — он может быть удален в будущей версии Client Profile.
                Пруф
                0
                расскажите как компилировать в память…
                дело в том, что он все равно пишет на диск временную сборку, а потом уже грузит в память
                  0
                  Ну так и компилировать :) Пишется-то она в .net temporary files, а то, что это — файлы на диске, а не в памяти — только конкретная реализация.
                +1
                Динамическая компиляция удобна когда есть часто изменяемый код. То есть таким образом мы используем код на C# как скрипт. Таким образом удобно вносить небольшие изменения на стороне заказчика без перекомпиляции всего проекта.
                  +2
                  CompilerResultsresults = provider.CompileAssemblyFromSource(compilerParams, source);
                  Может всетаки stringsource?
                    +1
                    это у меня Source Code Highlighter или Writer пробелы пообрезал :) Переменная то нормально была объявлена string source, и собственно ее имя — source :)
                    Спасибо, все поправил вроде.
                    0
                    Интересен как раз «другой вопрос» — то самое «зачем». Первое, что приходит в голову — генетические алгоритмы и полиморфные вирусы. Понятно, что можно и без динамической компиляции. Но с ней как-то естественнее :)

                    А кто что ещё интересненькое в эту тему может предложить?

                    Типа… автоматическую оптимизацию кода в зависимости от среды и вообще по обстоятельствам :) Код сам для себя время от времени запускает профайлер, анализирует, где у него образовались ботлнеки, пытается заменить эти куски кода на альтернативные (опционально — скачивая библиотеки, подходящие по интерфейсам).
                      +2
                      У нас используются клиентские скрипты, которые содержат бизнес-логику. Служба поддержки на стороне заказчика имеет инструмент для того, чтобы «подкрутить» функционал (добавить кастомную валидацию, скрыть поля ввода) и т.п. Так как у нас толстый клиент на WinForms, такое решение вполне устраивает всех.

                      Тот сценарий, что вы рассказали, называется PGO (profile guided optimization), и пока что вне среды разработки он доступен в зачаточном состоянии (ниже уровнем, на этапе jit-компиляции IL — «горячие» и «холодные» участки).
                        +1
                        Я, конечно же, подозревал (и надеялся), что что-то подобное реализовано или реализуется. Идейка ведь на поверхности. Другое дело, что реализация далеко не проста или не слишком-то эффективна пока что, раз так мало случаев применения.

                        Спасибо за наводку, теперь буду знать, как оно называется. Во всяком случае, в одной из своих ипостасей.

                        А вообще было бы интересно дожить до того времени, когда единожды установленные на моём компе программы будут менять код со временем. Не просто потому, что авторы обновили версию, а по более интеллектуальным и избирательным принципам. Приспосабливаясь к среде, приспосабливаясь ко мне — без участия админа. Хотя бы и в сторону сокращения функционала — ведь жалкие проценты использую.
                        +1
                        а) к примеру — вы пишите некое приложение (автоматизирующая бизнес область или нет), которое позволяет создавать методы для Domain объектов или функции, которые с ними работают. И в итоге вы предоставляете некий функционал, при помощи которого пользователь (в данному случае администратор системы) может создавать данные функции (не обязательно на C#) — главное что вы их запросто можете преобразовать в C# код, скомпилировать и положить предположим в dll рядом и потом вызывать.
                        б) создаете проект, чем то похожий на sql-ex, только на c#, чем не вариант?
                          +1
                          а) Сборка на заказ (типа плагинов, в данном случае). Что-то где-то вспоминается, что некоторые так целые дистрибутивы собирают. Поставил галочки через web-интерфейс — сгенерился архив лично для тебя, качай. Даже админ не нужен.

                          б) С базами данных редко дело имел, а гуглиться разное всякое. Так что не знаю, «чем не вариант» :)
                            +1
                            sql-ex.ru/ — это проект для обучения sql, вы пишите код, на сервере проверяется.
                          +3
                          Динамическая компиляция используется для написания плагинов в проекте Oxite. Правда пока у них нет релиза с такой функциональностью, но можно посмотреть в рабочей версии.
                            +2
                            Я компилирую поисковые запросы в предикаты и фильтрую коллекции объектов, так организован поиск в моих программах.

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

                            Только я использую Linq Expression Trees, а не предложенный метод. Linq Expression Trees в свою очередь используют класс DynamicMethod, наверное.
                              +3
                              Также мне кажется, предложенный метод с предикатами можно использовать для Linq to SQL и Entity Framework — библиотеки смогут успешно преобразовать типизированный предикат в SQL-выражение, что позволит пользователю делать эффективные запросы к БД (выполняемые на стороне сервера БД) в стиле Google.

                              Я пока работал с коллекциями обычных объектов и не экспериментировал с поиском в БД.

                              Вообще, я имел в виду, что запрос, введённый пользователем, типа «time:<5m AND rating:>3» преобразовывается в скомпилированный типизированный делегат: (Track track) => track.Time < TimeSpan.FromMinutes(5) && track.Rating > 3;

                              Если Track — это одна из сущностей из БД, то предикат можно обратно преобразовать в SQL и сделать выборку на стороне сервера, а не фильтровать всю коллекцию объектов на стороне клиента, как если бы запрос не компилировался.
                          • НЛО прилетело и опубликовало эту надпись здесь
                              +2
                              То что вам это не нужно, это ещё не значит, что это не нужно другим. Я вот представляю, как это можно использовать.

                              Спасибо автору топика!
                                +5
                                у вас питон мозга, уважаемый
                                  +1
                                  Вы мне, наверное, не поверите, но существуют программы, которые несколько более сложны, чем ваш хелловорлд. И если технология есть — ей найдется применение. Хуже, когда задача есть, а решить ее красиво нечем.

                                  P.S. Ну так, для справки — на IronPython, который для .net, ваш хелловорлд будет выглядеть идентично. Так что вы не путайте теплое с мягким.
                                    0
                                    А вам приходилось писать что-то кроме Хелловорлда, мне кажется нет, судя по тому как вы рассуждаете? У многих технологий уже есть занятая ими ниша, и это происходит обычно не потому что она (технология) хорошо пропиарена, а потому что она лучше других справлятся с возложенной на нее задачей или делает это на требуемом/достаточном уровне. Питон — хорошая технология, но есть задачи где его применения как минимум неуместно, что к примеру является следствием его скорости работы, которая не сравнится со скоростью работы .NET приложений.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                        +1
                                        Ну не будут на питоне полностью игры писать, куски логики — да, но не полноценные игры. Полноценные графические приложения тоже не будут писать ибо есть проблемы с многопоточностью и опять таки с прозводительностью. Приложения на .NET по скорости работы весьма адекватны, под Windows единственная альтернатива это С++, но страдает скорость разработки.
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                      +1
                                      Стоит прочитать топик более внимательно.
                                        +2
                                        Вы чем недовольны? Вот я вам динамически компилирую и запускаю Hello-World на C#. Что не устраивает?

                                        DynamicMethod meth = new DynamicMethod("", null, null);
                                        ILGenerator il = meth.GetILGenerator();
                                        il.EmitWriteLine("Hello, World!");
                                        il.Emit(OpCodes.Ret);
                                        
                                        Action hello = (Action) meth.CreateDelegate(typeof(Action));
                                        hello();
                                        


                                        Кстати, я сначала думал, что в топике именно об этом способе пойдет речь! Автор, надо было упомянуть и другие способы динамической компиляции!
                                          0
                                          Я тоже хотел побольше про DynamicMethod, хотя в продакшн никогда подобного не использовал ибо недебажится. То же самое с рефлекшном, в принципе.
                                        0
                                        Автору спасибо за труд. Наткнулся на статью через гуглопоиск. Изложенное пригодится мне для реализации моего проекта

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое