Создать метод расширения задачи для сопоставления

#c# #asynchronous #async-await

#c# #асинхронный #асинхронный-ожидание

Вопрос:

Чего я хочу

Мы используем AutoMapper, и весь наш уровень обслуживания является асинхронным. Вот как наше сопоставление выглядит в настоящее время в действии API:

 var response = (await this.service.GetAll())
    .AsQueryable()
    .ProjectTo<DataViewModel>();
...
  

Я хочу добиться чего-то вроде:

 var response = await this.service
    .GetAll()
    .Map<DataViewModel>();
  

Что я сделал

Лучшее, чего я достиг на данный момент, это:

 var response = await this.service
    .GetAll()
    .Map<DataServiceModel, DataViewModel>();
  

Методы расширения выглядят следующим образом:

 // Task extension
public static async Task<IEnumerable<TDestination>> MapCollection<TSource, TDestination>(
    this Task<IEnumerable<TSource>> collection)
        => (await collection)
            .MapCollection<TDestination>();

// IEnumerable extension
public static IEnumerable<TDestination> MapCollection<TDestination>(
    this IEnumerable collection)
{
    foreach (var item in collection)
    {
        yield return Mapper.Map<TDestination>(item);
    }
}
  

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

Проблема

Если я удалю TSource , я не смогу заставить компилятор согласиться со мной. Когда я пытаюсь использовать расширение, определенное следующим образом:

 public static async Task<IEnumerable<TDestination>> MapCollection<TDestination>(
    this Task<IEnumerable> collection)
        => (await collection)
            .MapCollection<TDestination>();
  

компилятор кричит:

Task<IEnumerable<DataServiceModel>> ‘ не содержит определения для ‘ MapCollection ‘, а для перегрузки наилучшего метода расширения ‘ TaskExtensions.MapCollection<DataViewModel>(Task<IEnumerable>) ‘ требуется приемник типа ‘ Task<IEnumerable>

Поэтому я не могу заставить его распознать мой метод, если нет явного универсального TSource . Я не понимаю, в чем проблема, поскольку компилятор согласен с моим IEnumerable расширением, которое определяется как

 MapCollection<TDestination>(this IEnumerable collection)
  

но не позволяет:

 MapCollection<TDestination>(this Task<IEnumerable> collection)
  

Вопросы

  1. Могу ли я как-то обойти вышеупомянутую проблему и определить мое Task расширение с единственным универсальным параметром, который ему действительно нужен?
  2. Создает ли такое использование Task какие-либо проблемы с производительностью? Т.Е. блоки потоков, взаимоблокировки и так далее. Я так не думаю, но мне все еще не хватает глубоких знаний в программировании на asnyc.

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

1.a Task<IEnumerable<Something>> это не a Task<IEnumerable> . Только потому, что два типа имеют определенную связь наследования / реализации, это не означает, что общий экземпляр, созданный поверх этих двух типов, демонстрирует одинаковую связь.

2. Может быть, изменить TSource на объект?

3. @Dmitriy не работает по той же причине, о которой, я полагаю, заявил Damien_The_Unbeliever .

Ответ №1:

Ваша самая большая проблема — это частичный вывод типа, и это невозможно сделать.

Однако, если вы не возражаете против дополнительного вызова, вам может сойти с рук и метод расширения, и класс-оболочка:

 public class Wrapper<TSource>
{
   private readonly Task<IEnumerable<TSource>> _sourceCollection;

   public Wrapper(Task<IEnumerable<TSource>> source) 
      =>_sourceCollection = source;
      
   public async Task<IEnumerable<TDest>> To<TDest>()
      => (IEnumerable<TDest>)Mapper.Map(await _sourceCollection, _sourceCollection.GetType(), typeof(IEnumerable<TDest>));

}

public static class Extensions
{
   public static Wrapper<TSource> Map<TSource>(this Task<IEnumerable<TSource>> source)
      => new Wrapper<TSource>(source);
}
  

Использование

 await service.GetAllAsync()
             .Map()
             .To<Something>();
  

Примечание 1: Это полностью непроверенный метод, и даже если он работает, не хватает базовых проверок работоспособности.

Примечание 2: Это также немного расточительно из-за дополнительного уровня асинхронности.

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

1. Спасибо за ваше предложение, я рассмотрю его. Тем временем, не могли бы вы, пожалуйста, подробнее остановиться на примечании 2 ? О каком виде отходов мы говорим?

2. @Alex совсем немного, каждый раз, когда вы вызываете await, компилятор создает класс, накладные расходы ничто по сравнению с накладными расходами при выполнении операции ввода-вывода. Вероятно, вы могли бы повысить эффективность, вызвав ConfigureAwait(false) в вызове map