Reputation: 31222
Consider the following case:
class A
{
public int Id;
}
class B : A
{
}
class Main
{
public async Task<int> Create(Type type)
{
MethodInfo method = this.GetType().GetMethod("Create", new Type[] { typeof(string) }).MakeGenericMethod(new Type[] { type });
A a = await (Task<A>)method.Invoke(this, new object[] { "humpf" });
return a.Id;
}
public async Task<T> Create<T>(string name) where T : A
{
T t = await Foo.Bar<T>(name);
return t;
}
}
Calling new Main().Create(typeof(B))
will fail with a
Unable to cast object of type '
System.Threading.Tasks.Task[B]
' to type 'System.Threading.Tasks.Task[A]
'
I don't quite understand because in this case, the Generic Create<T>
method can only return a Task<T>
where T
is always derived from 'A
', but maybe I'm missing an edge case here.
Besides that, how can I make this work? Thanks!
Upvotes: 13
Views: 5170
Reputation: 637
My option has form of extension method. It takes any instance of Task
and returns Task<object?>
with actual result for generic tasks or null
for non-generic ones.
internal static class TaskExtensions
{
public static async Task<object> ToGenericTaskAsync(this Task task)
{
await task;
var taskType = task.GetType();
if (!IsAssignableToGenericTaskType(taskType))
{
return null;
}
return task
.GetType()
.GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)!
.GetValue(task);
}
private static bool IsAssignableToGenericTaskType(Type type)
{
if (type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(Task<>) &&
type.GetGenericArguments()[0] != Type.GetType("System.Threading.Tasks.VoidTaskResult"))
{
return true;
}
return type.BaseType is not null && IsAssignableToGenericTaskType(type.BaseType);
}
}
Usage:
Task<object?> t1 = await Task.CompletedTask.ToGenericTaskAsync();
Task<object?> t2 = await Task.FromResult<int>(5).ToGenericTaskAsync();
Example in .NET Fiddle
Upvotes: 0
Reputation: 91
I needed to get task result in Castle Interceptor. This code works for me:
if (invocation.ReturnValue is Task task)
{
task.Wait();
var result = invocation.ReturnValue.GetType().GetProperty("Result").GetValue(task);
_cacheProvider.Set(_key, result, _duration);
}
Upvotes: 0
Reputation: 3625
The above solution really helped me. I made a small tweak to the @Lukazoid solution...
var resultProperty = typeof(Task<>).MakeGenericType(type).GetProperty("Result");
A a = (A)resultProperty.GetValue(task);
to
dynamic a = task.GetType().GetProperty("Result")?.GetValue(task);
Upvotes: -1
Reputation: 19416
As per my comment:
Unlike interfaces, concrete types such as
Task<TResult>
cannot be covariant. See Why is Task not co-variant?. SoTask<B>
cannot be assigned to aTask<A>
.
The best solution I can think of is to use the underlying type Task
to perform the await
like so:
var task = (Task)method.Invoke(this, new object[] { "humpf" });
await task;
Then you can use reflection to get the value of the Result
:
var resultProperty = typeof(Task<>).MakeGenericType(type).GetProperty("Result");
A a = (A)resultProperty.GetValue(task);
return a.Id;
Upvotes: 28