Introduction

edit

Meziantou.Framework.ObjectMethodExecutor calls a method from its MethodInfo, like MethodInfo.Invoke, and handles the asynchronous methods for you: ExecuteAsync awaits whatever the method returns, a Task, a ValueTask, an F# Async or any type with a GetAwaiter method, and gives you the result as an object.

// Synchronous method
var replaceMethod = typeof(string).GetMethod(nameof(string.Replace), [typeof(string), typeof(string)])!;
var replaceExecutor = ObjectMethodExecutor.Create(replaceMethod);
Assert.Equal("Hello Universe", await InvokeAsync(replaceExecutor, "Hello World", ["World", "Universe"]));

// Asynchronous method
var readToEndMethod = typeof(StringReader).GetMethod(nameof(StringReader.ReadToEndAsync), Type.EmptyTypes)!;
var readToEndExecutor = ObjectMethodExecutor.Create(readToEndMethod);
using var reader = new StringReader("Hello");
Assert.Equal("Hello", await InvokeAsync(readToEndExecutor, reader, []));

static async Task<object?> InvokeAsync(ObjectMethodExecutor executor, object instance, object?[] parameters)
{
    if (executor.IsMethodAsync)
    {
        return await executor.ExecuteAsync(instance, parameters);
    }
    else
    {
        return executor.Execute(instance, parameters);
    }
}

Use it in frameworks that call user code discovered at run time: command handlers, message handlers, plug-ins, test runners... The executor compiles a delegate for the method once, so the calls that follow do not pay the cost of reflection again.

Next steps