8

I'm currently working on an compiler in C#, where the behaviour is defined by LambdaExpressions, and then using CompileToMethod, transformed into MethodBuilders and saved to DLL. All functions are public and static.

However, I could not find a way to extract usable MethodInfo (or another method of reference) from the MethodBuilder until the behaviour is defined and declaring type is created/sealed. That means that at that until that point, it is impossible to use Expression.Call to call these functions. That makes self-recursion or mutual referencing between two functions impossible.

I ended up using Reflection to invoke the functions at runtime, but it's very suboptimal, and I'm still curious if there's a better way.

How do i ensure functions created with LambdaExpression.CompileToMethod(MethodBuilder) can self-call?

Alternatively, is there any other way to use LambdaExpressions which would allow this and support saving as a static method to a dll?

Kravaros
  • 222
  • 3
  • 8
  • You can always use y-combinator to convert non-recursive function to recursive one - https://stackoverflow.com/questions/31819718/using-the-y-combinator-in-c-sharp – Alexei Levenkov Apr 23 '20 at 03:41

1 Answers1

1

I hope this helps.
This is complete code example which produces runtime defined type with single static recursive method.
For the simplicity of the example the recursive method is infinite - at the end of the Main method the recursive method is called

static void Main(string[] args)
{
    var moduleBuilder = CreateDynamicModuleBuilder();
    var typeBuilder = moduleBuilder.DefineType("Person", TypeAttributes.Public | TypeAttributes.Class);
    var methodBuilder = typeBuilder.DefineMethod("SayHello", MethodAttributes.Static | MethodAttributes.Public);
    var methodExpression = CreateRecursiveExpression();
    var lambda = Expression.Lambda(methodExpression);
    lambda.CompileToMethod(methodBuilder);

    var typeInfo = typeBuilder.CreateType();
    var methodInfo = typeInfo.GetMethod("SayHello", BindingFlags.Public | BindingFlags.Static);
    methodInfo.Invoke(null, null);
}

private static Expression CreateRecursiveExpression()
{
    var methodInfo = typeof(Console).GetMethod("WriteLine", new[] { typeof(String) });
    var arg = Expression.Constant("Hello");
    var consoleCall = Expression.Call(methodInfo, arg);
    var sayHelloActionVariable = Expression.Variable(typeof(Action), "sayHelloAction");
    var block = Expression.Block(
        new[] { sayHelloActionVariable },
        Expression.Assign(
            sayHelloActionVariable,
            Expression.Lambda(
                Expression.Block(
                    consoleCall,
                    Expression.Invoke(sayHelloActionVariable)
                )
            )
        ),
        Expression.Invoke(sayHelloActionVariable)
    );

    return block;
}

private static ModuleBuilder CreateDynamicModuleBuilder()
{
    var name = new AssemblyName("Example.DynamicRecursion");
    var am = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
    var mb = am.DefineDynamicModule(name.Name, $"{name.Name}.dll");
    return mb;
}

This code will create type with the following signature

public class Person
{
   public static void SayHello()
   {
       Action sayHelloAction;
       sayHelloAction = () => 
       {
           Console.WriteLine("Hello");
           sayHelloAction();
       }
       sayHelloAction();
   }
}
vasil oreshenski
  • 2,788
  • 1
  • 14
  • 21
  • Thank you for your answer, however, this isn't exactly what i'm looking for. You created a method with a recursive behavior inside it, not a method that can be called recursively. While one could compile an entire program this way, it would have almost no performance benefits over interpreted code and in some cases would prevent calling these methods from other assemblies. – Kravaros Jun 03 '20 at 04:03
  • @Kravaros Hi, i've read your question again, and i think this won't be possible. CompileToMethod will access some of the properties of the Method Info, which at that point will throw 'not implemented exception'. The only thing that comes to my mind is to expose this methods as delegate properties of the class, which i think can be combined with Expression.Property - will try it on the weekend. – vasil oreshenski Jun 05 '20 at 05:22