#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)
Вопросы
- Могу ли я как-то обойти вышеупомянутую проблему и определить мое
Task
расширение с единственным универсальным параметром, который ему действительно нужен? - Создает ли такое использование
Task
какие-либо проблемы с производительностью? Т.Е. блоки потоков, взаимоблокировки и так далее. Я так не думаю, но мне все еще не хватает глубоких знаний в программировании на asnyc.
Комментарии:
1.a
Task<IEnumerable<Something>>
это не aTask<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