How to convert a Task<TDerived> to a Task<TBase>?

ChaseMedallion picture ChaseMedallion · Mar 20, 2013 · Viewed 8k times · Source

Since C#'s Task is a class, you obviously can't cast a Task<TDerived> to a Task<TBase>.

However, you can do:

public async Task<TBase> Run() {
    return await MethodThatReturnsDerivedTask();
}

Is there a static task method I can call to get a Task<TDerived> instance which essentially just points to the underlying task and casts the result? I'd like something like:

public Task<TBase> Run() {
    return Task.FromDerived(MethodThatReturnsDerivedTask());
}

Does such a method exist? Is there any overhead to using an async method solely for this purpose?

Answer

Stephen Cleary picture Stephen Cleary · Mar 20, 2013

Does such a method exist?

No.

Is there any overhead to using an async method solely for this purpose?

Yes. But it's the easiest solution.

Note that a more generic approach is an extension method for Task such as Then. Stephen Toub explored this in a blog post and I've recently incorporated it into AsyncEx.

Using Then, your code would look like:

public Task<TBase> Run()
{
  return MethodThatReturnsDerivedTask().Then(x => (TBase)x);
}

Another approach with slightly less overhead would be to create your own TaskCompletionSource<TBase> and have it completed with the derived result (using TryCompleteFromCompletedTask in my AsyncEx library):

public Task<TBase> Run()
{
  var tcs = new TaskCompletionSource<TBase>();
  MethodThatReturnsDerivedTask().ContinueWith(
      t => tcs.TryCompleteFromCompletedTask(t),
      TaskContinuationOptions.ExecuteSynchronously);
  return tcs.Task;
}

or (if you don't want to take a dependency on AsyncEx):

public Task<TBase> Run()
{
  var tcs = new TaskCompletionSource<TBase>();
  MethodThatReturnsDerivedTask().ContinueWith(t =>
  {
    if (t.IsFaulted)
      tcs.TrySetException(t.Exception.InnerExceptions);
    else if (t.IsCanceled)
      tcs.TrySetCanceled();
    else
      tcs.TrySetResult(t.Result);
  }, TaskContinuationOptions.ExecuteSynchronously);
  return tcs.Task;
}