3

I have this class (simplified example)

public class Foo
{
    public object Bar(Type type)
    {
        return new object();
    }
}

and I want to call the Bar method on an instance of Bar using DynamicMethod as it is shown below:

MethodInfo methodInfo = typeof(Foo).GetMethod(nameof(Foo.Bar), new[] { typeof(Type) });
DynamicMethod method = new DynamicMethod("Dynamic Bar", 
                                         typeof(object), 
                                         new []{ typeof(Type) }, 
                                         typeof(Foo).Module);

ILGenerator ilGenerator = method.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.EmitCall(OpCodes.Call, method, null); // I feel like this is wrong...
ilGenerator.Emit(OpCodes.Ret);

Func<Type, object> func = (Func<Type, object>) method.CreateDelegate(typeof(Func<Type, object>));

// Attempt to call the function:
func(typeof(Foo));

However, it does not work as wanted but rather aborts with

Process is terminated due to a StackOverFlowException.


Can someone please tell me what I am doing wrong? Is it a mismatch of the parameters? How can I call the Func on a specific instance of Bar?

Thomas Flinkow
  • 4,845
  • 5
  • 29
  • 65

1 Answers1

3
ilGenerator.EmitCall(OpCodes.Call, method, null); // I feel like this is wrong...

You are currently writing method; you probably intended to call methodInfo here. Note that this will need to be a static method to use Call - if it is an instance method, you should probably be using CallVirt. Since you aren't passing in an instance of Foo, it is unclear where the target instance is going to come from; you need to load two values onto the stack to call the instance method Foo.Bar(Type type) - and you're currently only loading one.

To show Delegate.CreateDelegate usage:

var methodInfo = typeof(Foo).GetMethod(nameof(Foo.Bar), new[] { typeof(Type) });
var foo = new Foo();

// if Foo is known ahead of time:
var f1 = (Func<Type, object>)Delegate.CreateDelegate(
    typeof(Func<Type, object>), foo, methodInfo);

// if Foo is only known per-call:
var f2 = (Func<Foo, Type, object>)Delegate.CreateDelegate(
    typeof(Func<Foo, Type, object>), null, methodInfo);

Console.WriteLine(f1(typeof(string)));

Console.WriteLine(f2(foo, typeof(string)));
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • `CallVirt` seems promising, as I want the method to be called on a specific instance. Thank you for the hint. Also, I would rather not use `Delegate.CreateDelegate`, because it prove to be kind of a performance bottleneck before. – Thomas Flinkow Mar 28 '18 at 10:27
  • 1
    @ThomasFlinkow compared to DynamicMethod? I think (based on over a decade of IL-emit work) that you're probably not going to get improved performance by using ILGenerator/MethodBuilder.CreateDelegate instead of Delegate.CreateDelegate. Either of these approaches is very effective **as long as** you cache the generated instance correctly. – Marc Gravell Mar 28 '18 at 10:30
  • 1
    @ThomasFlinkow also see my update about needing an instance of `Foo` – Marc Gravell Mar 28 '18 at 10:30
  • Thank you very much for your efforts. Right now I am using `MethodInfo.Invoke`, which is kind of slow. Therefore I was hoping to get improved performance by using IL. If I remember correctly, I used `MethodInfo.Invoke` as an improvement over `Delegate.CreateDelegate`, but I might be wrong here. Considering the instance of `Foo`, I was also wondering about where to put it. Could you please provide a little example on that? Thank you. – Thomas Flinkow Mar 28 '18 at 10:32
  • 1
    @ThomasFlinkow no, Delegate.CreateDelegate is **very** different to `MethodInfo.Invoke`, with `Delegate.CreateDelegate` being **much** faster, *as long as* you store the delegate and don't create it per-call. – Marc Gravell Mar 28 '18 at 10:34
  • 1
    @ThomasFlinkow well, where does the `Foo` instance come from? you can't "store" it on a `DynamicMethod` - there's nowhere to store it. Three options: you create a `Func` instead (and use arg0/arg1), or you create a `TypeBuilder` with a field, and a constructor that assigns the field, and use `ldarg0, ldfld_s {fld}, ldarg1`; or third option: `Delegate.CreateDelegate` **already takes a target instance** – Marc Gravell Mar 28 '18 at 10:36
  • Alright, thank you for that tip as well. I will look into that and see if it does improve performance in my case. Although I darkly remember that for some reason I couldn't use `Delegate.CreateDelegate`, I don't know what exactly it was anymore - I think generics. However, your post is a very good starting point, thank you very much. – Thomas Flinkow Mar 28 '18 at 10:37
  • 1
    @ThomasFlinkow added full example of `Delegate.CreateDelegate` for both the scenarios (`Foo` known ahead of time, vs `Foo` known per-call) – Marc Gravell Mar 28 '18 at 10:39
  • 1
    @ThomasFlinkow the main time that `Delegate.CreateDelegate` doesn't work is if you need to do non-trivial conversions as part of the call – Marc Gravell Mar 28 '18 at 10:40
  • @ThomasFlinkow sorry to spam with replies, but the important thing with `Delegate.CreateDelegate` is: make sure you always write it to a typed delegate; if you just store it as `Delegate` and use `DynamicInvoke`, then sure: you haven't saved anything and might as well have used reflection `Invoke` – Marc Gravell Mar 28 '18 at 10:41
  • No need to be sorry, I am very happy for all your highly qualified replies. It might be that I used `DynamicInvoke` back then, which explains why it was slower. – Thomas Flinkow Mar 28 '18 at 10:45