Сам себе Microsoft

    Мы уже рассмотрели способ создания встраиваемого скриптового движка на основе CodeDom.Compiler и класса CSharpCodeProvider. Поставим теперь более амбициозную задачу, где не будем полагаться на готовый компилятор. Будем писать свой собственный генератор, который строит MSIL-код «на лету» и исполняет его.

    Для начала, попробуем сложить два числа и распечатать результат как бы на C#, но не пользуясь его языковыми конструкциями:

    using System;
    using System.IO;
    using System.Reflection;
    using System.Reflection.Emit;
    
    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                Type[] par = new Type[] { typeof(Int32), typeof(Int32) };
                DynamicMethod func = new DynamicMethod("AddTwoValues", typeof(Int32), par, false);
                ILGenerator il = func.GetILGenerator();
    
                // это то же самое как: int AddTwoValues(int x, int y) { return x+y; }
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Add);
                il.Emit(OpCodes.Ret);
    
                Object[] param = new Object[] { 13, 12 };   // готовим параметры 12 и 13
    
                int iRet=(int)func.Invoke(null, param);     // выполняем
    
                // выводим что получилось
                Console.WriteLine("{0}+{1}={2}", param[0], param[1], iRet);  
            }
        }
    }

    Запускаем на исполнение:

    image

    Итак, в простейшем случае мы уже смогли сгенерировать свой собственный MSIL-код для сложения двух чисел. На этом уже вполне можно было бы написать приложение с функциональностью старого калькулятора «Электроника», который с 80-х годов прошлого века пылится у меня в столе.
    Теперь добавим в сгенерированный код вызовы функций, например, пусть параметры распечатываются непосредственно в том коде, который создавался нами «на лету»:
    using System;
    using System.IO;
    using System.Reflection;
    using System.Reflection.Emit;
    
    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                Type[] par = new Type[] { typeof(Int32), typeof(Int32) };
                DynamicMethod func = new DynamicMethod("AddTwoValues", typeof(Int32), par, false);
                ILGenerator il = func.GetILGenerator();
    
                MethodInfo fnWriteLine = typeof(Console).
                                         GetMethod("WriteLine", new Type[] { typeof(Int32) });
    
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, fnWriteLine);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Call, fnWriteLine);
    
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Add);
                il.Emit(OpCodes.Ret);
    
                Object[] param = new Object[] { 13, 12 };   // готовим параметры 12 и 13
    
                int iRet=(int)func.Invoke(null, param);     // выполняем
    
                // выводим что получилось
                Console.WriteLine("{0}+{1}={2}", param[0], param[1], iRet);
            }
        }
    }
    

    Запускаем на исполнение:
    image
    Итак, теперь мы умеем генерировать MSIL-код таким образом, что из него можно вызывать функции. Обратите внимание, что мы генерируем непосредственно MSIL в памяти без всяких промежуточных файлов. И в нашей власти теперь всё, мы можем менять генерацию кода как угодно – например, для нотификации чего-либо вставлять через строчку вызов какого-либо callback-метода или что-то ещё.
    Что нас ещё отделяет от написания собственного языка программирования? Да только формальный синтаксис и его парсер! Если мы напишем соответствующий парсер, то ничто нам не сможет помешать создать свой собственный язык программирования, например, с таким синтаксисом:

    ЕСЛИ КЛИЕНТ АКТИВЕН
    НАЧАЛО БЛОКА
    КУПИТЬ БИЛЕТ НА ПОЕЗД 120 на 31/12/2017 КУПЕЙНЫЙ НИЖНЕЕ
    РАСПЕЧАТАТЬ БЛАНК
    КОНЕЦ БЛОКА

    О парсере и формальном синтаксисе разговор пойдёт в следующий статье.
    Аркадий Пчелинцев, архитектор проектов
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      +4
      В 2017 году уже разрешено использовать Expression вместо DynamicMethod.

      var x = 13;
      var y = 12;
      var sum = Expression.Lambda<Func<int>>
      (
      	Expression.Add
      	(
      		Expression.Constant(x),
      		Expression.Constant(y)
      	)
      ).Compile().Invoke();
      
        0

        Динамичный метод не быстрее ли будет?

          0
          Нет. Динамический метод еще неудобный, вероятность ошибки выше и требуются знания как работает IL.
            0
            Деревья выражений компилятся в тот же динамический метод, но затем все это попадает в анонимную сборку (Assembly) для выполнения в «песочнице», что имеет оверхед. Отсюда вывод, что все-таки сгенерированный на IL код быстрее. С помощью BenchmarkDotNet можно написать тесты и увидеть разницу.
              0
              >Отсюда вывод, что все-таки сгенерированный на IL код быстрее
              Что быстрее чего? Динамический метод быстрее динамического метода?
              >для выполнения в «песочнице», что имеет оверхед
              Ну вот тут бы не помешала ссылка на документацию.
                0
                Статья Сергея Теплякова в его майкрософт блоге. Статья затрагивает кодогенерацию и есть тесты производительности как раз с примером генерации динамического метода через деревья выражений и «руками». Отличается тем, что руками можно указать к какому типу привязать новый метод и сделать его таким образом доверенным — в отличие от деревьев выражений, где компиляция в динамический метод не дает такой возможности и метод создается в анонимной сборке, выполнение которой происходит в песочнице, что в свою очередь приводит к оверхеду. Проверка безопасности здесь логична, ведь метод анонимный и надо проверять куда и к кому Вы в теле метода обращаетесь (например надо проверить не обращаетесь ли Вы к методам, модификаторы доступа которых запрещают Вам это делать из чужой сборки/класса — internal/private и т.д.).
                0
                сгенерированный на IL код быстрее. С помощью BenchmarkDotNet можно написать тесты и увидеть разницу.

                Сделано.
            +1
            По идее, Expression.* достаточно плотно привязаны к C#\VB.NET. Если автор ставит себе задачу создать собсвтенный язык программирования, то используя Expression он получит скорее транспайлер.
            –1
            del, промахнулся
              0

              Only users with full accounts can post comments. Log in, please.