11

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,441
  • 11
  • 62
  • 75
  • 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

3 Answers3

25

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
  • 18,199
  • 3
  • 62
  • 83
  • 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
  • 61
  • 3
-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,319
  • 26
  • 32