13

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!

Razzie
  • 30,834
  • 11
  • 63
  • 78
  • Unlike interfaces, concrete types such as `Task` cannot be covariant. See [Why is Task not co-variant?](http://stackoverflow.com/questions/30996986/why-is-taskt-not-co-variant). So `Task` cannot be assigned to a `Task`. – Lukazoid Jan 15 '16 at 15:52
  • Thanks for that link! That helps. Now how do I work around this? :-) – Razzie Jan 15 '16 at 15:56

4 Answers4

28

As per my comment:

Unlike interfaces, concrete types such as Task<TResult> cannot be covariant. See Why is Task not co-variant?. So Task<B> cannot be assigned to a Task<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;
Community
  • 1
  • 1
Lukazoid
  • 19,016
  • 3
  • 62
  • 85
  • Why not just `task.GetType()` instead of `typeof(Task<>).MakeGenericType(type)` ? – huysentruitw Aug 10 '17 at 14:18
  • 2
    `task.GetType()` would most likely always be fine, but if the resulting `Task` was a derived type which shadowed `Result` it would throw an `AmbiguousMatchException`. Like this that can never possibly happen as I am being more explicit over where I expect to find the `Result` property. – Lukazoid Aug 10 '17 at 15:27
  • @JohnLeidegren I have no idea but it's possible, so I think it's safer to cater for that possibility. – Lukazoid May 23 '19 at 09:25
  • @Lukazoid I'm using reflection to unwrap the task result because I don't know the type. If I knew the type I would just cast the value to `Task`. – John Leidegren May 24 '19 at 20:57
  • @JohnLeidegren How would you do that cast if you only knew the type at runtime, like in the question? – Lukazoid May 26 '19 at 19:27
  • @Lukazoid if the type parameter is dynamic you can't. – John Leidegren May 27 '19 at 12:05
0

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);
}
onets
  • 91
  • 3
0

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 (!taskType.IsGenericType 
            || taskType.GetGenericTypeDefinition() != typeof(Task<>)
            || taskType.GetGenericArguments()[0] == Type.GetType("System.Threading.Tasks.VoidTaskResult"))
        {
            return null;
        }

        return task
            .GetType()
            .GetProperty(nameof(Task<object>.Result), BindingFlags.Instance | BindingFlags.Public)!
            .GetValue(task);
    }
}

Usage:

Task<object?> t1 = await Task.CompletedTask.ToGenericTaskAsync();
Task<object?> t2 = await Task.FromResult<int>(5).ToGenericTaskAsync();

Example in .NET Fiddle

-1

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);
LawMan
  • 3,469
  • 1
  • 29
  • 32