Ответ 1
Итак, для начала первое выражение может вызвать только первую перегрузку. Это не допустимое выражение для Func<Task>
, потому что есть путь к коду, который возвращает недопустимое значение (void
вместо Task
).
() => while(true)
фактически является допустимым методом для любой подписи. (Он, наряду с реализациями, такими как () => throw new Expression();
, являются действительными органами методов, которые возвращают любой возможный тип, включая void
, интересную точку пустяков и почему автогенерируемые методы из IDE обычно просто генерируют исключение; компиляция независимо от сигнатуры метода.) Метод, который бесконечно циклически представляет собой метод, в котором отсутствуют коды кода, которые не возвращают правильное значение (и это истинно, является ли "правильное значение" void
, Task
, или буквально все остальное). Это, конечно, потому, что он никогда не возвращает значение, и он делает это так, как может доказать компилятор. (Если бы это было сделано так, что компилятор не смог доказать, поскольку он не решил проблему остановки в конце концов, тогда мы были бы в той же лодке, что и A
.)
Итак, для нашего бесконечного цикла, который лучше, учитывая, что применимы оба перегрузки. Это приводит нас к нашему секрету блеска спецификаций С#.
Если перейти к разделу 7.4.3.3, маркер 4, мы видим:
Если E является анонимной функцией, T1 и T2 являются типами делегатов или типами дерева выражений с одинаковыми списками параметров, а для D в X-конце этого списка параметров (§7.4.2.11) существует допустимый тип возвращаемого типа X (§7.4.2.11):
[...]
если T1 имеет возвращаемый тип Y, а T2 возвращается в пустоту, тогда C1 является лучшим преобразованием.
Поэтому при преобразовании из анонимного делегата, который мы делаем, он предпочтет преобразование, которое возвращает значение, превышающее значение void
, поэтому оно выбирает Func<Task>
.