.Net Standard 中 CompileToMethod 的替代方案
Alternatives of CompileToMethod in .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 : Unable to import a global method or
field from a different module.
我知道我可以手动发出 IL,但我需要准确地将 Expression
-> 转换为绑定到特定 TypeBuilder
的 MethodInfo
,而不是自己构建 DynamicMethod
它。
这不是一个理想的解决方案,但如果您不想从头开始编写所有内容,则值得考虑:
- 如果您查看
CompileToMethod
实现,您会发现它在内部使用 LambdaCompiler
class.
- 如果你深入挖掘,你会发现
LambdaCompiler
使用 System.Reflection.Emit
将 lambda 转换为 MethodInfo
。
System.Reflection.Emit
受 .NET Core 支持。
- 考虑到这一点,我的提议是尽量重用
LambdaCompiler
源代码。你可以找到它 here.
此解决方案的最大问题在于:
LambdaCompiler
分布在许多文件中,因此查找编译它所需的内容可能很麻烦。
LambdaCompiler
可能会使用一些 .NET Core 根本不支持的 API。
一些补充意见:
- 如果要检查哪个平台支持哪个 API,请使用 .NET API Catalog。
- 如果您想查看 .NET 标准版本之间的差异,请使用 this 网站。
我 运行 在将一些代码移植到 netstandard 时遇到了同样的问题。我的解决方案是使用 Compile 方法将 lambda 编译为 Func,将 Func 存储在我添加到动态类型的静态字段中,然后在我的动态方法中,我只需从该静态字段加载并调用 Func。这允许我使用 LINQ 表达式 API 而不是反射发射(这会很痛苦)来创建 lambda,但仍然让我的动态类型实现一个接口(这是我的场景的另一个要求)。
感觉有点像 hack,但它有效,并且可能比尝试通过 LambdaCompiler 重新创建 CompileToMethod 功能更容易。
正在尝试让 LambdaCompiler 在 .NET Core 上运行
基于 Michal Komorowski 的回答,我决定尝试将 LambdaCompiler
移植到 .NET Core。你可以找到我的努力 here (GitHub link)。 class 分布在多个文件中这一事实确实是这里最小的问题之一。一个更大的问题是它依赖于 .NET Core 代码库中的内部部分。
从上面的 GitHub 回购中引用我自己的话:
Unfortunately, it is non-trivial because of (at least) the following issues:
AppDomain.CurrentDomain.DefineDynamicAssembly is unavailable in .NET Core - AssemblyBuilder.DefineDynamicAssembly replaces is. This SO answer describes how it can be used.
Assembly.DefineVersionInfoResource is unavailable.
Reliance on internal methods and properties, for example BlockExpression.ExpressionCount, BlockExpression.GetExpression, BinaryExpression.IsLiftedLogical etc.
For the reasons given above, an attempt to make this work as a standalone package is quite unfruitful in nature. The only realistic way to get this working would be to include it in .NET Core proper.
This, however, is problematic for other reasons. I believe the licensing is one of the stumbling stones here. Some of this code was probably written as part of the DLR effort, which it itself Apache-licensed. As soon as you have contributions from 3rd party contributors in the code base, relicensing it (to MIT, like the rest of the .NET Core codebase) becomes more or less impossible.
其他选项
我认为你目前最好的选择,取决于用例,是这两个之一:
使用 DLR (Dynamic Language Runtime), available from NuGet (Apache 2.0-licensed). This is the runtime that empowers IronPython,据我所知,这是目前唯一积极维护的 DLR-powered 语言。 (IronRuby 和 IronJS 似乎都被有效地抛弃了。)DLR 允许您使用 Microsoft.Scripting.Ast.LambdaBuilder
定义 lambda 表达式;这似乎并没有被 IronPython 直接使用。还有那个Microsoft.Scripting.Interpreter.LightCompiler
class 好像挺有意思的
不幸的是,DLR 的文档似乎很少。我认为 the CodePlex site 引用了一个 wiki,但它处于离线状态(虽然可以通过在 CodePlex 上下载存档来访问)。
使用 Roslyn 为您编译(动态)代码。这可能也有一点学习曲线;不幸的是,我自己还不是很熟悉。
这似乎有很多精选的 links、教程等: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
这是罗斯林的其他一些通用读物 link。然而,这些 link 中的大多数都专注于 分析 C# 代码(这是 Roslyn 的用例之一),但 Roslyn 可用于 生成 IL 代码(即“编译”)C# 代码。
还有第三个选项,我们大多数人可能都没有兴趣:
- 使用System.Reflection.Emit directly, to generate the IL instructions. This is the approach used by e.g. the F# compiler.
免责声明:我是图书馆的作者。
我创建了 Expression Compiler,它与 Linq Expressions 相似 API,只是略有不同。 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 下发布的。我们正在积极开发它,我们在 JavaScript 中添加了生成器和 async/await 的功能,因此您可以创建可调试的 JavaScript 代码和 运行 C# 代码,而不是使用 Expression Compiler轻松融入其中。
我现在正在将一些使用表达式的库移植到 .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 : Unable to import a global method or field from a different module.
我知道我可以手动发出 IL,但我需要准确地将 Expression
-> 转换为绑定到特定 TypeBuilder
的 MethodInfo
,而不是自己构建 DynamicMethod
它。
这不是一个理想的解决方案,但如果您不想从头开始编写所有内容,则值得考虑:
- 如果您查看
CompileToMethod
实现,您会发现它在内部使用LambdaCompiler
class. - 如果你深入挖掘,你会发现
LambdaCompiler
使用System.Reflection.Emit
将 lambda 转换为MethodInfo
。 System.Reflection.Emit
受 .NET Core 支持。- 考虑到这一点,我的提议是尽量重用
LambdaCompiler
源代码。你可以找到它 here.
此解决方案的最大问题在于:
LambdaCompiler
分布在许多文件中,因此查找编译它所需的内容可能很麻烦。LambdaCompiler
可能会使用一些 .NET Core 根本不支持的 API。
一些补充意见:
- 如果要检查哪个平台支持哪个 API,请使用 .NET API Catalog。
- 如果您想查看 .NET 标准版本之间的差异,请使用 this 网站。
我 运行 在将一些代码移植到 netstandard 时遇到了同样的问题。我的解决方案是使用 Compile 方法将 lambda 编译为 Func,将 Func 存储在我添加到动态类型的静态字段中,然后在我的动态方法中,我只需从该静态字段加载并调用 Func。这允许我使用 LINQ 表达式 API 而不是反射发射(这会很痛苦)来创建 lambda,但仍然让我的动态类型实现一个接口(这是我的场景的另一个要求)。
感觉有点像 hack,但它有效,并且可能比尝试通过 LambdaCompiler 重新创建 CompileToMethod 功能更容易。
正在尝试让 LambdaCompiler 在 .NET Core 上运行
基于 Michal Komorowski 的回答,我决定尝试将 LambdaCompiler
移植到 .NET Core。你可以找到我的努力 here (GitHub link)。 class 分布在多个文件中这一事实确实是这里最小的问题之一。一个更大的问题是它依赖于 .NET Core 代码库中的内部部分。
从上面的 GitHub 回购中引用我自己的话:
Unfortunately, it is non-trivial because of (at least) the following issues:
AppDomain.CurrentDomain.DefineDynamicAssembly is unavailable in .NET Core - AssemblyBuilder.DefineDynamicAssembly replaces is. This SO answer describes how it can be used.
Assembly.DefineVersionInfoResource is unavailable.
Reliance on internal methods and properties, for example BlockExpression.ExpressionCount, BlockExpression.GetExpression, BinaryExpression.IsLiftedLogical etc.
For the reasons given above, an attempt to make this work as a standalone package is quite unfruitful in nature. The only realistic way to get this working would be to include it in .NET Core proper.
This, however, is problematic for other reasons. I believe the licensing is one of the stumbling stones here. Some of this code was probably written as part of the DLR effort, which it itself Apache-licensed. As soon as you have contributions from 3rd party contributors in the code base, relicensing it (to MIT, like the rest of the .NET Core codebase) becomes more or less impossible.
其他选项
我认为你目前最好的选择,取决于用例,是这两个之一:
使用 DLR (Dynamic Language Runtime), available from NuGet (Apache 2.0-licensed). This is the runtime that empowers IronPython,据我所知,这是目前唯一积极维护的 DLR-powered 语言。 (IronRuby 和 IronJS 似乎都被有效地抛弃了。)DLR 允许您使用
Microsoft.Scripting.Ast.LambdaBuilder
定义 lambda 表达式;这似乎并没有被 IronPython 直接使用。还有那个Microsoft.Scripting.Interpreter.LightCompiler
class 好像挺有意思的不幸的是,DLR 的文档似乎很少。我认为 the CodePlex site 引用了一个 wiki,但它处于离线状态(虽然可以通过在 CodePlex 上下载存档来访问)。
使用 Roslyn 为您编译(动态)代码。这可能也有一点学习曲线;不幸的是,我自己还不是很熟悉。
这似乎有很多精选的 links、教程等: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
这是罗斯林的其他一些通用读物 link。然而,这些 link 中的大多数都专注于 分析 C# 代码(这是 Roslyn 的用例之一),但 Roslyn 可用于 生成 IL 代码(即“编译”)C# 代码。
还有第三个选项,我们大多数人可能都没有兴趣:
- 使用System.Reflection.Emit directly, to generate the IL instructions. This is the approach used by e.g. the F# compiler.
免责声明:我是图书馆的作者。
我创建了 Expression Compiler,它与 Linq Expressions 相似 API,只是略有不同。 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 下发布的。我们正在积极开发它,我们在 JavaScript 中添加了生成器和 async/await 的功能,因此您可以创建可调试的 JavaScript 代码和 运行 C# 代码,而不是使用 Expression Compiler轻松融入其中。