28

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?

svick
  • 236,525
  • 50
  • 385
  • 514
ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152

3 Answers3

42

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;
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Why not just `return MethodThatReturnsDerivedTask().ContinueWith(task => (TBase)task.Result, TaskContinuationOptions.ExecuteSynchronously)`? – Sam Harwell Jan 05 '14 at 03:17
  • 5
    If you use `Result`, you'll wrap any exceptions in an `AggregateException` and complete faulted if the original task completed in a canceled state. That said, with some more code to handle those situations correctly, you *could* make a working solution using `ContinueWith` without `TaskCompletionSource`. – Stephen Cleary Jan 05 '14 at 13:52
  • 2
    Hi Stephen! I think that a `TaskScheduler.Default` is missing here. – Theodor Zoulias Mar 19 '22 at 09:45
20

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

There is no built-in method for this, and this does cause overhead.

The "lightest weight" alternative would be to use a TaskCompletionSource<T> to create a new task for this. This could be done via an extension method like so:

static Task<TBase> FromDerived<TBase, TDerived>(this Task<TDerived> task) where TDerived : TBase
{
     var tcs = new TaskCompletionSource<TBase>();

     task.ContinueWith(t => tcs.SetResult(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
     task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions), TaskContinuationOptions.OnlyOnFaulted);
     task.ContinueWith(t => tcs.SetCanceled(), TaskContinuationOptions.OnlyOnCanceled);

     return tcs.Task;
}
khellang
  • 17,550
  • 6
  • 64
  • 84
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
0

you can try this : task.ContinueWith<TDerived>( t => (TDerived)t.Result);