Aserian
Aserian

Reputation: 1117

C# MethodInfo to Callable Func - Expression Tree with generic return type

I am trying to take a MethodInfo and convert it into a callable Func. I have been successfully able to do this with a synchronous function of with method signature:

public AnyObject MyFuncName(AnotherObject message, IMessage messageMetadata) 

Where AnyObject and AnotherObject is just any class. I have done so with the below code, after extracting the MethodInfo with reflection:

public static Func<Handler, object, IMessage, object> CompileHandler(Type handler, MethodInfo method, Type argumentType)
    {
        var instance = Expression.Parameter(typeof(Handler), "instance");
        var messageParam = Expression.Parameter(typeof(object), "message");
        var messageMetaParam = Expression.Parameter(typeof(IMessage), "messageMetadata");
        var methodCall = Expression.Call(
            Expression.Convert(instance, handler),
            method,
            Expression.Convert(messageParam, argumentType),
            Expression.Convert(messageMetaParam, typeof(IMessage)));

        return Expression.Lambda<Func<Handler, object, IMessage, object>>(
            methodCall,
            instance,
            messageParam,
            messageMetaParam).Compile();
    }

The above works. It will create the Func and I am able to call the function. I am now trying to make the function I am reflecting on asynchronous and will have the exact signature except it will be of return type Task<AnyObject> that gets converted to a Func<Handler, object, IMessage, Task<object>>

However, when I change all references above of Func<Handler, object, IMessage, object> to Func<Handler, object, IMessage, Task<object>>

When I do this, Expression.Lambda throws this exception:

Initialization method HandlerUnitTests.TestInitialize 
threw exception. System.ArgumentException: System.ArgumentException: Expression of type 
'System.Threading.Tasks.Task`1[Handler.AnyObject]' cannot be used for return type 
'System.Threading.Tasks.Task`1[System.Object]'

However it had no such objections when casting the return type to object when the Func signature was Func<Handler, object, IMessage, object>

Edit: The question here is how do I achieve the above?

Upvotes: 0

Views: 382

Answers (1)

Jeremy Lakeman
Jeremy Lakeman

Reputation: 11100

You could pass the return value through your own method to cast the result;

private static async Task<object> Unwrap<T>(Task<T> task) => await task;
...
Expression.Call(
    new Func<Task<object>, Task<object>>(Unwrap).Method
        .GetGenericMethodDefinition()
        .MakeGenericMethod(typeof(...)),
    methodCall
)

But it does sound like you're solving the wrong problem. Your handler should probably be generic. Perhaps something like;

public abstract class Handler{
   public abstract Task<object> InvokeHandler(object message);
}

public abstract class Handler<M>:Handler{
    public abstract object HandleMessage(M message);
    public virtual Task<object> HandleMessageAsync(M message)
        => Task.FromResult(HandleMessage(message));
    public override Task<object> InvokeHandler(object message)
        => HandleMessageAsync((M)message);
}

Now your main loop can call any Handler.InvokeHandler, while all type conversions occur in the abstract generic base type. And without any runtime compilation.

Upvotes: 1

Related Questions