ContinueWith для задачи неизвестного универсального типа

#c# #asynchronous #dynamic #task #continuewith

#c# #асинхронный #динамический #задача #continuewith

Вопрос:

У меня есть метод, который получает a Task<T> , где T неизвестно во время компиляции, и an IAsyncDisposable . Мне нужно вернуть a Task<T> , который автоматически удаляет одноразовый объект после завершения исходной задачи.

Это то, что я придумал до сих пор, но это выдает ошибку времени компиляции

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

Ошибка, которую я получаю, это

Не удается преобразовать асинхронное лямбда-выражение в тип делегирования ‘Func<динамический, динамический>’. Асинхронное лямбда-выражение может возвращать void, Task или Task, ни один из которых не преобразуется в ‘Func<динамический, динамический>’.

Я уже пробовал разные комбинации dynamic и Task , но не смог создать рабочее решение. Я всегда получаю ошибки компиляции или времени выполнения.

Редактировать

Потому что кажется непонятным, почему я делаю это таким образом:

Я использую это внутри IAsyncQueryProvider s ExecuteAsync -метода. Интерфейс определяет сигнатуру методов следующим образом

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

Исходя из этого, я знаю TResult , что это либо тип IAsyncEnumerable<T> , либо Task<T> . Я уже написал код для обработки случая, когда это an IAsyncEnumerable<T> , но я все еще борюсь, когда это a Task<T> .

Комментарии:

1. Пожалуйста, опубликуйте пример того, где вы используете свой AutoDispose метод.

2. Вообще говоря, смешивать dynamic с дженериками — плохая идея. Это очень разные вещи. Я думаю, что этого лучше избегать dynamic полностью, потому что это в первую очередь сводит на нет смысл использования статически типизированного языка, такого как C #.

3. » T неизвестно во время компиляции и an «, я на самом деле не могу пройти мимо этого утверждения, оно кажется довольно неправильным. Дженерики компилируются во время компиляции, они ДОЛЖНЫ быть известны.

4. @TheGeneral T неизвестно во время компиляции, если вы создаете библиотеку с открытыми обобщениями.

5. @Dai означает несвязанный универсальный тип ? Однако они не могут использоваться ни в каком выражении, кроме typeof … Редактировать… ах, хорошо, отмените это, я понимаю, о чем вы говорите. хороший момент. 🙂

Ответ №1:

Для использования async тип результата во время компиляции должен быть Task (или похожий на задачу). Вложенные задачи обычно используются ContinueWith с async продолжениями; Unwrap это одно из решений. Если вы можете полностью избежать ContinueWith этого, это еще лучше, но я считаю, что в этом случае это потребует довольно утомительной работы с дженериками.

Что-то вроде этого должно работать:

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

Ответ №2:

Попробуйте это?

 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 resu<
}
 

Пример использования:

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

HttpResponseMessage resp = await task;
// `resp` will be in a disposed state at this point.
 

Комментарии:

1. AutoDispose Метод не может быть асинхронным и должен возвращать TResult , а Task<TResult> также не TResult имеет типа Task<T> , поэтому его перенос приведет к чему-то вроде Task<Task<T>> . Я использую это внутри Execute метода AsyncQueryProviders, поэтому у меня есть эти ограничения.