0

I have a function that is building a dynamic method. As part of this dynamic method, it is calling an action known at generation time. As a minimal reproducible example, consider the following C# code:

using System.Reflection.Emit;

static class Program {
  static Action MakeAction(Action action) {
    DynamicMethod method = new DynamicMethod("test", null, null);
    ILGenerator generator = method.GetILGenerator();
    generator.Emit(OpCodes.Call, action.Method);
    generator.Emit(OpCodes.Ret);
    return method.CreateDelegate<Action>();
  }

  static void Main() {
    MakeAction(() => Console.WriteLine("hello"))();
  }
}

When I run this, I get a System.InvalidProgramException. What is the correct way to do this?

Benji Dial
  • 131
  • 7
  • Why are you trying to use reflection to figure out the method the delegate points to and call it directly rather than just invoking the delegate? It seems unnecessarily complicated and error prone and gives you way more to think about than you need to. If you *do* just want to execute an arbitrary method defined by a methdoinfo, why accept a delegate at all and not just accept the method info directly? – Servy Dec 23 '21 at 15:23
  • Passing the MethodInfo directly is a good idea, I don't know why I didn't think of that. The Action that is passed to it is either gotten from another DynamicMethod (which can be passed as a MethodInfo) or is a lambda. Changing the lambdas to static methods and using the MethodInfo of that static method works, although it is a little less clean. – Benji Dial Dec 23 '21 at 18:26

1 Answers1

0

Use that delegate

If you have a delegate and you will invoke it you can use it exactly instead of creating a new delegate. Just static Action MakeAction(Action action) => action.

Static or instance delegates

Delegates can be made of static or instance methods so you must make specific call for both.

Static methods calls is simple: no this argument, just call and ret.

For instance method calls you must load target object to stack and run callvirt (null-check and allows virtual methods). ILGenerator does not allow you to load some object from other memory. DynamicMethod allows you to create static method that will be instance delegate. For this you must add this argument and add owner class for method.

Final solution:

static Action MakeAction(Action action)
{
    // static method
    if (action.Method.IsStatic)
    {
        DynamicMethod method = new DynamicMethod("test", null, null);
        ILGenerator generator = method.GetILGenerator();
        generator.Emit(OpCodes.Call, action.Method);
        generator.Emit(OpCodes.Ret);
        return method.CreateDelegate<Action>();
    }
    // instance method
    else
    {
        DynamicMethod method = new DynamicMethod("test", null, new[] { typeof(object) }, typeof(object));
        ILGenerator generator = method.GetILGenerator();
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Callvirt, action.Method);
        generator.Emit(OpCodes.Ret);
        return method.CreateDelegate<Action>(action.Target);
    }
}

System.Linq.Expressions

You can use Expressions for better runtime method building. It is much easier that DynamicMethod and Emit.

Creating delegate from delegate in Linq.Expressions:

static Action MakeAction(Action action)
{
    Expression target = action.Target == null ? null : Expression.Constant(action.Target);
    Expression body = Expression.Call(target, action.Method);
    Expression<Action> expr = Expression.Lambda<Action>(body);
    return expr.Compile();
}

Creating delegate from lambda-expression:

static Action MakeAction(Expression<Action> expr)
{
    return expr.Compile();
}
groser
  • 319
  • 1
  • 11