1

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>.

wertzui
  • 5,148
  • 3
  • 31
  • 51
  • Please post an example of where you use your `AutoDispose` method. – Dai Nov 19 '20 at 23:28
  • Generally speaking it's a bad idea to mix `dynamic` with generics. They're very different things. I think it's best to avoid `dynamic` entirely because it defeats the point of using a statically-typed language like C# in the first place. – Dai Nov 19 '20 at 23:35
  • "*T is unknown at compile time and an*", I actually cant get past this statement, its seems fairly incorrect. Generics ***ARE*** compiled in at compile time, they ***HAVE*** to be known. – TheGeneral Nov 19 '20 at 23:43
  • @TheGeneral `T` is unknown at compile-time if you're building a library with open generics. – Dai Nov 19 '20 at 23:50
  • @Dai do mean an *unbound generic type*? However, they cant be used in any sort of expression other than `typeof`... Edit... ahh ok, cancel that, i see what you are saying. good point. :) – TheGeneral Nov 19 '20 at 23:54
  • I edited my question to make it clear why I'm using it this way. – wertzui Nov 20 '20 at 07:04

2 Answers2

3

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;
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

Try this?

private static async Task<TResult> AutoDispose<TResult>( this Task<TResult> task )
    where TResult : IAsyncDisposable
{
    TResult result = await task.ConfigureAwait(false);
    if( result != null )
    {
        await result.DisposeAsync().ConfigureAwait(false);
    }
    
    return result;
}

Example usage:

Task<HttpResponseMessage> task = new HttpClient()
    .GetAsync( "/foo" )
    .AutoDispose();

HttpResponseMessage resp = await task;
// `resp` will be in a disposed state at this point.
Dai
  • 141,631
  • 28
  • 261
  • 374
  • The `AutoDispose` method cannot be async and must return `TResult` and not `Task` also `TResult` is of type `Task`, so wrapping it would result in something like `Task>`. I'm using this inside an AsyncQueryProviders `Execute` method, so I have these constraints. – wertzui Nov 20 '20 at 06:58