Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives of CompileToMethod in .Net Standard

I'm now porting some library that uses expressions to .Net Core application and encountered a problem that all my logic is based on LambdaExpression.CompileToMethod which is simply missing in. Here is sample code:

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);

    ...
}

Is it possible to rewrite it somehow to make it possible to generate methods using Expressions? I already can do it with Emit but it's quite complex and i'd like to avoid it in favor of high-level Expressions.

I tried to use var method = expression.Compile().GetMethodInfo(); but in this case I get an error:

System.InvalidOperationException : Unable to import a global method or field from a different module.

I know that I can emit IL manually, but I need exactly convert Expression -> to MethodInfo binded to specific TypeBuilder instead of building myself DynamicMethod on it.

like image 964
Alex Zhukovskiy Avatar asked Jan 07 '17 10:01

Alex Zhukovskiy


4 Answers

It is not an ideal solution but it is worth considering if you don't want to write everything from the scratch:

  1. If you look on CompileToMethod implementation, you will see that under the hood it uses internal LambdaCompiler class.
  2. If you dig even deeper, you willl see that LambdaCompiler uses System.Reflection.Emit to convert lambdas into MethodInfo.
  3. System.Reflection.Emit is supported by .NET Core.
  4. Taking this into account, my proposition is to try to reuse the LambdaCompiler source code. You can find it here.

The biggest problem with this solution is that:

  1. LambdaCompiler is spread among many files so it may be cumbersome to find what is needed to compile it.
  2. LambdaCompiler may use some API which is not supported by .NET Core at all.

A few additional comments:

  1. If you want to check which API is supported by which platform use .NET API Catalog.
  2. If you want to see differences between .NET standard versions use this site.
like image 152
Michał Komorowski Avatar answered Nov 20 '22 07:11

Michał Komorowski


Disclaimer: I am author of the library.

I have created Expression Compiler, which has similar API to that of Linq Expressions, with slight changes. 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));

This library is part of JavaScript compiler we have created. We are actively developing it and we have added features of generators and async/await in JavaScript, so Instead of using Expression Compiler, you can create debuggable JavaScript code and run C# code easily into it.

like image 21
Akash Kava Avatar answered Nov 20 '22 09:11

Akash Kava


I ran into the same issue when porting some code to netstandard. My solution was to compile the lambda to a Func using the Compile method, store the Func in a static field that I added to my dynamic type, then in my dynamic method I simply load and call the Func from that static field. This allows me to create the lambda using the LINQ Expression APIs instead of reflection emit (which would have been painful), but still have my dynamic type implement an interface (which was another requirement for my scenario).

Feels like a bit of a hack, but it works, and is probably easier than trying to recreate the CompileToMethod functionality via LambdaCompiler.

like image 3
MarkPflug Avatar answered Nov 20 '22 08:11

MarkPflug


Attempting to get LambdaCompiler working on .NET Core

Building on Michal Komorowski's answer, I decided to give porting LambdaCompiler to .NET Core a try. You can find my effort here (GitHub link). The fact that the class is spread over multiple files is honestly one of the smallest problems here. A much bigger problem is that it relies on internal parts in the .NET Core codebase.

Quoting myself from the GitHub repo above:

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.

Other options

I think your best bet at the moment, depending on the use case, is one of these two:

  • Use the DLR (Dynamic Language Runtime), available from NuGet (Apache 2.0-licensed). This is the runtime that empowers IronPython, which is to the best of my knowledge the only actively maintained DLR-powered language out there. (Both IronRuby and IronJS seems to be effectively abandoned.) The DLR lets you define lambda expressions using Microsoft.Scripting.Ast.LambdaBuilder; this doesn't seem to be directly used by IronPython though. There is also the Microsoft.Scripting.Interpreter.LightCompiler class which seems to be quite interesting.

    The DLR unfortunately seems to be quite poorly documented. I think there is a wiki being referred to by the CodePlex site, but it's offline (can probably be accessed by downloading the archive on CodePlex though).

  • Use Roslyn to compile the (dynamic) code for you. This probably has a bit of a learning curve as well; I am myself not very familiar with it yet unfortunately.

    This seems to have quite a lot of curated links, tutorials etc: https://github.com/ironcev/awesome-roslyn. I would recommend this as a starting point. If you're specifically interested in building methods dynamically, these also seem worth reading:

    • 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

    Here are some other general Roslyn reading links. Most of these links are however focused on analyzing C# code here (which is one of the use cases for Roslyn), but Roslyn can be used to generate IL code (i.e. "compile") C# code as well.

    • The .NET Compiler Platform SDK: https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/
    • Get started with syntax analysis: https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis
    • Tutorial: Write your first analyzer and code fix: https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix

There is also the third option, which is probably uninteresting for most of us:

  • Use System.Reflection.Emit directly, to generate the IL instructions. This is the approach used by e.g. the F# compiler.
like image 2
Per Lundberg Avatar answered Nov 20 '22 08:11

Per Lundberg