DEV Community

loading...
Cover image for Dynamic method invocation with .NET Core

Dynamic method invocation with .NET Core

David Guida
Microsoft MVP | Mythological creature, part Italian, part pizza. Programmer in the spare time. Solving coding problems he didn't know he had.
・3 min read

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);
}
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode
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 });
Enter fullscreen mode Exit fullscreen mode
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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

Results

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!

Discussion (0)