26

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
  • 225,720
  • 49
  • 378
  • 501
ChaseMedallion
  • 20,111
  • 13
  • 84
  • 146

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;
}
Stephen Cleary
  • 406,130
  • 70
  • 637
  • 767
  • 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
  • 1
    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
  • 16,656
  • 5
  • 61
  • 83
Reed Copsey
  • 539,124
  • 75
  • 1,126
  • 1,354
1

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