Альтернативы 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.
Это не идеальное решение, но его стоит рассмотреть, если вы не хотите писать все с нуля:
- Если вы посмотрите на
CompileToMethod
реализации, вы увидите, что под капотом он использует внутреннийLambdaCompiler
учебный класс. - Если вы будете копать еще глубже, вы увидите, что
LambdaCompiler
использованияSystem.Reflection.Emit
конвертировать лямбды вMethodInfo
, System.Reflection.Emit
поддерживается.NET Core.- Принимая это во внимание, мое предложение состоит в том, чтобы попытаться повторно использовать
LambdaCompiler
исходный код. Вы можете найти это здесь.
Самая большая проблема с этим решением заключается в том, что:
LambdaCompiler
распространяется по многим файлам, поэтому может быть сложно найти то, что необходимо для его компиляции.LambdaCompiler
может использовать какой-то API, который вообще не поддерживается.NET Core.
Несколько дополнительных комментариев:
- Если вы хотите проверить, какой API поддерживается какой платформой, используйте .NET API Catalog.
- Если вы хотите увидеть различия между стандартными версиями.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. Я бы рекомендовал это в качестве отправной точки. Если вас особенно интересует динамическое построение методов, их тоже стоит прочитать:
- https://gunnarpeipman.com/using-roslyn-to-build-object-to-object-mapper/
- http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn
Вот еще несколько общих ссылок для чтения Roslyn. Однако большинство этих ссылок сосредоточено здесь на анализе кода C# (что является одним из вариантов использования Roslyn), но Roslyn также может использоваться для генерации кода IL (т. Е. "Компиляции") кода C#.
- Пакет SDK для платформы компилятора.NET: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/
- Начните с анализа синтаксиса: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis
- Учебное пособие: напишите свой первый анализатор и исправление кода: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix
Есть и третий вариант, который, наверное, большинству из нас неинтересен:
- Используйте System.Reflection.Emit напрямую, чтобы сгенерировать инструкции IL. Это подход, используемый, например, компилятором F#.