Search code examples
c#asynchronousdynamictaskcontinuewith

ContinueWith for a Task of unknown generic type


I have a method that gets a Task<T> where T is unknown at compile time and an IAsyncDisposable. I need to return a Task<T> which automatically disposes the disposable object after the original task has been completed.

This is what I came up with so far, but it is throwing a compile time Error

private static TResult AutoDispose<TResult>(TResult result, IAsyncDisposable asyncDisposable)
{
    Func<dynamic, dynamic> continuationFunction = async t => { await asyncDisposable.DisposeAsync(); return ((dynamic)t).Result; };
    var autoDisposing = ((dynamic)result).ContinueWith(continuationFunction);
    return (TResult)autoDisposing;
}

The Error, I'm getting is

Cannot convert async lambda expression to delegate type 'Func<dynamic, dynamic>'. An async lambda expression may return void, Task or Task, none of which are convertible to 'Func<dynamic, dynamic>'.

I already tried different combinations of dynamic and Task but was unable to create a working solution. I'm always getting compile- or runtime errors.

EDIT

Because it seems to be confusing why I'm doing it this way:

I'm using this inside an IAsyncQueryProviders ExecuteAsync method. The Interface defines the methods Signature as following

public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)

Based on this, I know TResult is either of type IAsyncEnumerable<T> or Task<T>. I already wrote code to handle the case when it is an IAsyncEnumerable<T> but I'm still struggling when it is a Task<T>.


Solution

  • To use async, the compile-time result type must be Task (or a tasklike). Nested tasks are normal using ContinueWith with async continuations; Unwrap is one solution. If you can avoid ContinueWith completely, that's even better, but I believe in this case that would require quite a bit of tedious generics work.

    Something like this should work:

    private static TResult AutoDispose<TResult>(TResult result, IAsyncDisposable asyncDisposable) // where TResult: Task<T>
    {
      Func<dynamic, Task<TResult>> continuationFunction = async () => { await asyncDisposable.DisposeAsync(); return result; };
      var continuation = ((dynamic)result).ContinueWith(continuationFunction, TaskScheduler.Default);
      var newResult = TaskExtensions.Unwrap(continuation);
      return (TResult)newResult;
    }