How to call a generic async method using reflection
Solution 1:
Because Task<T>
derives from Task
you can await on just that, once the task is awaited you can use reflection to safely access the .Result
property via reflection.
Once you have the result you will either need to store it in a IBar
and use the methods and properties on that or cast to the specific type after testing to use the type specific methods.
Here is a full MCVE of it
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Test().Wait();
Console.ReadLine();
}
static async Task Test()
{
var foo = new Foo();
var bar2 = new Bar2();
object resultObject = await CallGetByReflection(foo, bar2);
IBar result = (IBar)resultObject;
result.WriteOut();
//or
if (resultObject is Bar)
{
((Bar)resultObject).Something();
}
else if (resultObject is Bar2)
{
((Bar2)resultObject).SomethingElse();
}
}
private static async Task<object> CallGetByReflection(IFoo foo, IBar bar)
{
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar.GetType());
var task = (Task) generic.Invoke(foo, new[] {bar});
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty.GetValue(task);
}
public interface IBar
{
void WriteOut();
}
public class Bar : IBar
{
public void Something()
{
Console.WriteLine("Something");
}
public void WriteOut()
{
Console.WriteLine(nameof(Bar));
}
}
public class Bar2 : IBar
{
public void SomethingElse()
{
Console.WriteLine("SomethingElse");
}
public void WriteOut()
{
Console.WriteLine(nameof(Bar2));
}
}
public interface IFoo
{
Task<T> Get<T>(T o) where T : IBar;
}
public class Foo : IFoo
{
public async Task<T> Get<T>(T o) where T : IBar
{
await Task.Delay(100);
return o;
}
}
}
}
UPDATE: Here is a extension method to simplify the process
public static class ExtensionMethods
{
public static async Task<object> InvokeAsync(this MethodInfo @this, object obj, params object[] parameters)
{
var task = (Task)@this.Invoke(obj, parameters);
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty.GetValue(task);
}
}
This turns CallGetByReflection
in to
private static Task<object> CallGetByReflection(IFoo foo, IBar bar)
{
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar.GetType());
return generic.InvokeAsync(foo, new[] {bar});
}
UPDATE 2: Here is a new extension method that works with any awaitable type instead of only tasks by using dynamic
and GetAwaiter()
public static class ExtensionMethods
{
public static async Task<object> InvokeAsync(this MethodInfo @this, object obj, params object[] parameters)
{
dynamic awaitable = @this.Invoke(obj, parameters);
await awaitable;
return awaitable.GetAwaiter().GetResult();
}
}
Solution 2:
Based on your example you know type of returned object at compile time -> IFoo
, so you can use normal casting (IFoo)
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(typeof(IBar));
var task = (Task<IBar>)generic.Invoke(foo, new [] { bar2 });
IBar result = await task;
If you don't know a type at compile time, then simply use dynamic
keyword
var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar2.GetType());
dynamic task = generic.Invoke(foo, new [] { bar2 });
IBar result = await task;
But if type of the task not a Task<iFoo>
at runtime - exception will be thrown
And if you need concrete type of IBar
then
var concreteResult = Convert.ChangeType(result, bar2.GetType());
Solution 3:
On top of @ScottChamberlain answer (which is great 😉) I'll suggest a small improvement on InvokeAsync
method to return Task<T>
rather than Task<object>
. Besides that it would be useful having a second method returning Task
, which isn't supported by InvokeAsync
.
using System.Threading.Tasks;
namespace System.Reflection
{
public static class MethodInfoExtensions
{
public static async Task<T> InvokeAsync<T>(this MethodInfo methodInfo, object obj, params object[] parameters)
{
dynamic awaitable = methodInfo.Invoke(obj, parameters);
await awaitable;
return (T)awaitable.GetAwaiter().GetResult();
}
public static async Task InvokeAsync(this MethodInfo methodInfo, object obj, params object[] parameters)
{
dynamic awaitable = methodInfo.Invoke(obj, parameters);
await awaitable;
}
}
}