Альтернативы CompileToMethod в.Net Standard

Я сейчас портирую некоторую библиотеку, которая использует выражения для .Net Core приложение и столкнулся с проблемой, что вся моя логика основана на LambdaExpression.CompileToMethod который просто отсутствует в. Вот пример кода:

public static MethodInfo CompileToInstanceMethod(this LambdaExpression expression, TypeBuilder tb, string methodName, MethodAttributes attributes)
{
    ...

    var method = tb.DefineMethod($"<{proxy.Name}>__StaticProxy", MethodAttributes.Private | MethodAttributes.Static, proxy.ReturnType, paramTypes);
    expression.CompileToMethod(method);

    ...
}

Можно ли как-то переписать его, чтобы можно было генерировать методы с использованием выражений? Я уже могу сделать это с Emit но это довольно сложно, и я хотел бы избежать этого в пользу высокоуровневых выражений.

Я пытался использовать var method = expression.Compile().GetMethodInfo(); но в этом случае я получаю ошибку:

System.InvalidOperationException: невозможно импортировать глобальный метод или поле из другого модуля.

Я знаю, что я могу излучать IL вручную, но мне нужно точно конвертировать Expression -> к MethodInfo привязан к конкретному TypeBuilder вместо того, чтобы строить себя DynamicMethod в теме.

4 ответа

Решение

Я столкнулся с той же проблемой при переносе некоторого кода в Netstandard. Мое решение состояло в том, чтобы скомпилировать лямбда-функцию в Func с помощью метода Compile, сохранить Func в статическом поле, которое я добавил к своему динамическому типу, затем в своем динамическом методе я просто загружаю и вызываю Func из этого статического поля. Это позволяет мне создавать лямбда-выражения с использованием API-интерфейсов LINQ Expression вместо Emit Reflection (что было бы болезненно), но при этом мой динамический тип реализовывал интерфейс (что было еще одним требованием для моего сценария).

Похоже на хакерство, но это работает, и, вероятно, это проще, чем пытаться воссоздать функциональность CompileToMethod через LambdaCompiler.

Это не идеальное решение, но его стоит рассмотреть, если вы не хотите писать все с нуля:

  1. Если вы посмотрите на CompileToMethod реализации, вы увидите, что под капотом он использует внутренний LambdaCompiler учебный класс.
  2. Если вы будете копать еще глубже, вы увидите, что LambdaCompiler использования System.Reflection.Emit конвертировать лямбды в MethodInfo,
  3. System.Reflection.Emit поддерживается.NET Core.
  4. Принимая это во внимание, мое предложение состоит в том, чтобы попытаться повторно использовать LambdaCompiler исходный код. Вы можете найти это здесь.

Самая большая проблема с этим решением заключается в том, что:

  1. LambdaCompiler распространяется по многим файлам, поэтому может быть сложно найти то, что необходимо для его компиляции.
  2. LambdaCompiler может использовать какой-то API, который вообще не поддерживается.NET Core.

Несколько дополнительных комментариев:

  1. Если вы хотите проверить, какой API поддерживается какой платформой, используйте .NET API Catalog.
  2. Если вы хотите увидеть различия между стандартными версиями.NET, используйте этот сайт.

Отказ от ответственности: я являюсь автором библиотеки.

Я создал компилятор выражений, API которого аналогичен API выражений Linq, с небольшими изменениями. https://github.com/yantrajs/yantra/wiki/Expression-Compiler

      var a = YExpression.Parameter(typeof(int));
var b = YExpression.Parameter(typeof(int));

var exp = YExpression.Lambda<Func<int,int,int>>("add",
            YExpression.Binary(a, YOperator.Add, b),
            new YParameterExpression[] { a, b });

var fx = exp.CompileToStaticMethod(methodBuilder);

Assert.AreEqual(1, fx(1, 0));
Assert.AreEqual(3, fx(1, 2));

Эта библиотека является частью созданного нами компилятора JavaScript и выпущена под лицензией LGPL. Мы активно его развиваем и добавили функции генераторов и async / await в JavaScript, поэтому вместо использования Expression Compiler вы можете создавать отлаживаемый код JavaScript и легко запускать в нем код C#.

Попытка заставить LambdaCompiler работать с.NET Core

Основываясь на ответе Михала Коморовского, я решил портировать LambdaCompilerв.NET Core попробуйте. Вы можете найти мои усилия здесь (ссылка на GitHub). Тот факт, что класс распределен по нескольким файлам, честно говоря, является одной из самых маленьких проблем здесь. Гораздо более серьезная проблема заключается в том, что он полагается на внутренние части в кодовой базе.NET Core.

Цитируя себя из репозитория GitHub выше:

К сожалению, это нетривиально из-за (как минимум) следующих проблем:

  • AppDomain.CurrentDomain.DefineDynamicAssembly недоступен в.NET Core - AssemblyBuilder.DefineDynamicAssembly заменяет его. Этот SO-ответ описывает, как его можно использовать.

  • Assembly.DefineVersionInfoResource недоступен.

  • Использование внутренних методов и свойств, например BlockExpression.ExpressionCount, BlockExpression.GetExpression, BinaryExpression.IsLiftedLogical и т. Д.

По причинам, указанным выше, попытка сделать эту работу как отдельный пакет совершенно бесплодна. Единственный реальный способ заставить это работать - это включить его в.NET Core.

Однако это проблематично по другим причинам. Я считаю, что лицензирование - один из камней преткновения. Часть этого кода, вероятно, была написана в рамках проекта DLR, который был лицензирован Apache. Как только у вас есть вклады сторонних разработчиков в базу кода, перелицензирование ее (в MIT, как и остальную часть кодовой базы.NET Core) становится более или менее невозможным.

Другие опции

Я думаю, что ваш лучший выбор на данный момент, в зависимости от варианта использования, - это один из следующих двух:

  • Используйте DLR (динамическую среду выполнения), доступную в NuGet (под лицензией Apache 2.0). Это среда выполнения, которая расширяет возможности IronPython, который, насколько мне известно, является единственным активно поддерживаемым языком на базе DLR. (И от IronRuby, и от IronJS, похоже, отказались.) DLR позволяет определять лямбда-выражения, используя Microsoft.Scripting.Ast.LambdaBuilder; Однако похоже, что это не используется IronPython напрямую. Также есть Microsoft.Scripting.Interpreter.LightCompiler класс, который кажется довольно интересным.

    К сожалению, DLR плохо документирован. Я думаю, что сайт CodePlex ссылается на вики, но она не в сети (хотя, вероятно, ее можно получить, загрузив архив на CodePlex).

  • Используйте Roslyn для компиляции (динамического) кода за вас. Это, вероятно, также требует некоторого обучения; Сам я, к сожалению, не очень хорошо с ним знаком.

    Кажется, здесь довольно много рекомендованных ссылок, руководств и т. Д.: https://github.com/ironcev/awesome-roslyn. Я бы рекомендовал это в качестве отправной точки. Если вас особенно интересует динамическое построение методов, их тоже стоит прочитать:

    Вот еще несколько общих ссылок для чтения Roslyn. Однако большинство этих ссылок сосредоточено здесь на анализе кода C# (что является одним из вариантов использования Roslyn), но Roslyn также может использоваться для генерации кода IL (т. Е. "Компиляции") кода C#.

Есть и третий вариант, который, наверное, большинству из нас неинтересен:

Другие вопросы по тегам