Dynamic method invocation with .NET Core

Dynamic method invocation with .NET Core

2021, Feb 05    

Hi All! Today we’re going to explore few ways to perform “dynamic method invocation” with .NET Core.

As some of you know already, a while ago I started working on an open-source project, OpenSleigh. It’s a Saga management library for .NET Core applications.

I have been focusing on it a lot in the last period, and this, unfortunately, led me to be less diligent with my blog. I have several articles in my backlog, looking for the right time to jump off the hat.

Anyways, while working on the first prototypes of OpenSleigh (BTW, make sure to at least fork or star the repository!), I had to face a bunch of times an interesting problem.

Let me try to summarize it very quickly:

What if we have to call a method on some instance, but the only thing we know is the method signature and not the class type?

It’s a tricky situation, but luckily for us, .NET has a few ways to get to the destination.

The problem now is: which one is the best?

So I decided to give it a try and write some benchmarks. I’ve tested:

  1. direct method invocation
  2. MethodInfo.Invoke
  3. Delegate.DynamicInvoke
  4. Func<> invocation
  5. dynamic cast

Of course, direct method invocation is used as a comparison, a baseline for all the other techniques.

Let’s suppose we have this small class here:

public class Foo
{
    public int Bar(int a, int b, bool c) => a + (c ? b : 0);
}

And we want to call Bar() on an instance, but all we have is an object. Let’s take a look at each technique.

MethodInfo.Invoke

We simply start by storing a MethodInfo reference to Bar() and then we simply invoke it:

object fooInstance = ...; // we get this from somewhere else
MethodInfo barMethod = ClassType.GetMethod(nameof(Foo.Bar));
barMethod.Invoke(fooInstance, new[] { (object)1, (object)2, (object)false });
Delegate.DynamicInvoke

In this case, instead, we start off by getting a MethodInfo reference, but we wrap it into a typed Delegate:

object fooInstance = ...; // we get this from somewhere else
MethodInfo barMethod = ClassType.GetMethod(nameof(Foo.Bar));
var delegateType = Expression.GetDelegateType(typeof(Foo), typeof(int), typeof(int), typeof(bool), typeof(int));
var @delegate = Delegate.CreateDelegate(delegateType, barMethod);
@delegate.DynamicInvoke(new[] { fooInstance, (object)1, (object)2, (object)false });
Func<> invocation

Similar to the previous one, but we also cast the Delegate to a Func<>:

object fooInstance = ...; // we get this from somewhere else
MethodInfo barMethod = ClassType.GetMethod(nameof(Foo.Bar));
var delegateType = Expression.GetDelegateType(typeof(Foo), typeof(int), typeof(int), typeof(bool), typeof(int));
var func = (Func<Foo, int, int, bool, int>)Delegate.CreateDelegate(delegateType, barMethod);
func(fooInstance as Foo, 1, 2, false);

This one seems a little bit “shady”, since we actually know that the instance is of type Foo. But still, I decided to add it to the group since it might still come useful.

Dynamic cast

This one is the easiest to code (after the direct invocation of course), as it’s basically just a cast to dynamic and a method call:

object fooInstance = ...; // we get this from somewhere else
dynamic dynamicFoo = fooInstance as dynamic;
dynamicFoo.Bar(1, 2, false);

So, after all this fuss, who’s the winner? I guess an image is worth thousand words:

Results <figcaption>dynamic method invocation</figcaption></figure>

So as you can see, calling a method directly, is definitely the fastest way. Followed immediately by the “shady” Func<> call and then the dynamic cast.

As usual, I’ve pushed the code to GitHub, so feel free to run your experiments and let me know!