Почему async/await допускает неявное преобразование из списка в IEnumerable?
Я просто играл с асинхронным/ожиданием и узнал что-то интересное. Взгляните на приведенные ниже примеры:
// 1) ok - obvious
public Task<IEnumerable<DoctorDto>> GetAll()
{
IEnumerable<DoctorDto> doctors = new List<DoctorDto>
{
new DoctorDto()
};
return Task.FromResult(doctors);
}
// 2) ok - obvious
public async Task<IEnumerable<DoctorDto>> GetAll()
{
IEnumerable<DoctorDto> doctors = new List<DoctorDto>
{
new DoctorDto()
};
return await Task.FromResult(doctors);
}
// 3) ok - not so obvious
public async Task<IEnumerable<DoctorDto>> GetAll()
{
List<DoctorDto> doctors = new List<DoctorDto>
{
new DoctorDto()
};
return await Task.FromResult(doctors);
}
// 4) !! failed to build !!
public Task<IEnumerable<DoctorDto>> GetAll()
{
List<DoctorDto> doctors = new List<DoctorDto>
{
new DoctorDto()
};
return Task.FromResult(doctors);
}
Рассмотрим случаи 3 и 4. Единственное отличие состоит в том, что 3 использует ключевые слова async/await. 3 строит отлично, однако 4 дает ошибку о неявном преобразовании List в IEnumerable:
Cannot implicitly convert type
'System.Threading.Tasks.Task<System.Collections.Generic.List<EstomedRegistration.Business.ApiCommunication.DoctorDto>>' to
'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<EstomedRegistration.Business.ApiCommunication.DoctorDto>>'
Что это значит, что здесь изменяются ключевые слова async/wait?
Ответы
Ответ 1
Task<T>
просто не является ковариантным типом.
Хотя List<T>
можно преобразовать в IEnumerable<T>
, Task<List<T>>
connot быть преобразованным в Task<IEnumerable<T>>
. И в # 4, Task.FromResult(doctors)
возвращает Task<List<DoctorDto>>
.
В № 3 мы имеем:
return await Task.FromResult(doctors)
Это то же самое, что:
return await Task<List<DoctorDto>>.FromResult(doctors)
Это то же самое, что:
List<DoctorDto> result = await Task<List<DoctorDto>>.FromResult(doctors);
return result;
Это работает, потому что List<DoctorDto>
может быть преобразован IEnumerable<DoctorDto>
.
Ответ 2
Подумайте о своих типах. Task<T>
не вариант, поэтому он не конвертируется в Task<U>
, даже если T : U
.
Однако, если t
- Task<T>
, то тип await t
равен t
, а t
может быть преобразован в U
, если T : U
.
Ответ 3
Ясно, что вы понимаете, почему List<T>
можно хотя бы вернуть как IEnumerable<T>
: просто потому, что он реализует этот интерфейс.
Также ясно, что третий пример делает что-то "лишнее", чем четвертое. Как говорили другие, 4-я неудача из-за отсутствия ковариантности (или наоборот), я никогда не могу вспомнить, в каком направлении они идут!), Потому что вы прямо пытаетесь предложить экземпляр Task<List<DoctorDto>>
в качестве экземпляра Task<IEnumerable<DoctorDto>>
.
Причина, по которой проходит третий проход, состоит в том, что await
добавляет большой кусок "кода поддержки", чтобы заставить его работать по назначению. Этот код разрешает Task<T>
на T
, так что return await Task<something>
возвращает тип, закрытый в общем Task<T>
, в этом случае something
.
То, что подпись метода затем возвращает Task<T>
, и она работает, снова решается компилятором, который требует Task<T>
, Task
или void
для асинхронных методов и просто массирует ваш T
обратно в Task<T>
как часть всего фона, созданного asyn/await продолжением gubbins.
Это добавленный шаг получения T
от await
и его нужно перевести обратно в Task<T>
, который дает ему пространство, в котором он должен работать. Вы не пытаетесь использовать существующий экземпляр Task<U>
для удовлетворения Task<T>
, вместо этого вы создаете новый Task<T>
, предоставляя ему U : T
, а при построении неявное литье происходит так, как вы ожидали (точно так же, как вы ожидаете работать IEnumerable<T> myVar = new List<T>();
).
Убей/поблагодарить компилятор, я часто делаю; -)