ObjectMethodExecutor Class

Provides a unified way to invoke synchronous and asynchronous methods via reflection.

[RequiresDynamicCode("ObjectMethodExecutor compiles expression trees and may construct generic types at runtime.")]
[RequiresUnreferencedCode("ObjectMethodExecutor performs reflection on arbitrary types.")]
public sealed class ObjectMethodExecutor

Remarks

This class creates optimized delegates for method invocation, avoiding repeated reflection overhead. It supports both synchronous and asynchronous methods, including those returning Task, ValueTask, or any custom awaitable type that implements the awaitable pattern.

Example

Basic usage with synchronous and asynchronous methods:

// 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);
    }
}

Properties

NameDescription
IsMethodAsyncGets a value indicating whether the method returns an awaitable type.

Gets a value indicating whether the method returns an awaitable type.

public bool IsMethodAsync { get; }

Property value

bool

Methods

NameDescription
Create(MethodInfo)Creates an executor for the specified method.
Create(MethodInfo, object?[]?)Creates an executor for the specified method with parameter default values.
Execute(object?, object?[]?)Executes the configured method on target. This can be used whether or not the configured method is asynchronous.
ExecuteAsync(object?, object?[]?)Executes the configured method on target.
GetDefaultValueForParameter(int)Gets the default value supplied for the parameter at index.

Create overloads

Create(MethodInfo)

ObjectMethodExecutor.cs

Creates an executor for the specified method.

public static ObjectMethodExecutor Create(MethodInfo methodInfo)
Parameters
methodInfo MethodInfo
The method to be invoked.
Returns

ObjectMethodExecutor An ObjectMethodExecutor that can invoke the specified method.

Create(MethodInfo, object?[]?)

ObjectMethodExecutor.cs

Creates an executor for the specified method with parameter default values.

public static ObjectMethodExecutor Create(MethodInfo methodInfo, object?[]? parameterDefaultValues)
Parameters
methodInfo MethodInfo
The method to be invoked.
parameterDefaultValues object?[]?
The default values for the method parameters, readable afterwards through ObjectMethodExecutor.GetDefaultValueForParameter. Must contain exactly one entry per parameter.
Returns

ObjectMethodExecutor An ObjectMethodExecutor that can invoke the specified method.

Execute(object?, object?[]?)

ObjectMethodExecutor.cs

Executes the configured method on target. This can be used whether or not the configured method is asynchronous.

public object? Execute(object? target, object?[]? parameters)

Parameters

target object?
The object whose method is to be executed.
parameters object?[]?
Parameters to pass to the method.

Returns

object? The method return value.

Exceptions

ArgumentException
parameters does not contain one entry per method parameter.

Remarks

Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than ExecuteAsync if you know at compile time what the return type is, because then you can directly "await" that value (via a cast), and then the generated code will be able to reference the resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated code will have to treat the resulting awaitable as a boxed object, because it doesn't know at compile time what type it would be.

ExecuteAsync(object?, object?[]?)

ObjectMethodExecutor.cs

Executes the configured method on target.

public ObjectMethodExecutorAwaitable ExecuteAsync(object? target, object?[]? parameters)

Parameters

target object?
The object whose method is to be executed.
parameters object?[]?
Parameters to pass to the method.

Returns

ObjectMethodExecutorAwaitable An object that you can "await" to get the method return value.

Exceptions

ArgumentException
parameters does not contain one entry per method parameter.
InvalidOperationException
The configured method is an async void method, which cannot be awaited. Use ObjectMethodExecutor.Execute instead.

Remarks

If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations as compared with using Execute and then using "await" on the result value typecasted to the known awaitable type. The possible extra heap allocations are for: 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally it's a reference type, and you normally create a new instance per call). 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance of it, and if it is, it will have to be boxed so the calling code can reference it as an object). 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling code doesn't know what type it's going to be).

GetDefaultValueForParameter(int)

ObjectMethodExecutor.cs

Gets the default value supplied for the parameter at index.

public object? GetDefaultValueForParameter(int index)

Parameters

index int
The zero-based index of the parameter.

Returns

object? The default value supplied for that parameter when the executor was created.

Exceptions

InvalidOperationException
No parameter default values were supplied to ObjectMethodExecutor.Create.
ArgumentOutOfRangeException
index is negative or not less than the number of parameters.

Remarks

Callers use this to build the parameters array passed to ObjectMethodExecutor.Execute or ObjectMethodExecutor.ExecuteAsync when they have no value of their own for a parameter. The executor itself never consults these values.