Подтвердить что ты не робот

Сложное разрешение перегрузки с одновременным (истинным)

Я реализовал перегрузки sync/async, когда столкнулся с этой странной ситуацией:

Когда у меня есть регулярное лямбда-выражение без параметров или возвращаемое значение, оно переходит к перегрузке Run с параметром Action, который предсказуем. Но когда эта лямбда имеет while (true), она переходит к перегрузке с параметром Func.

public void Test()
{
    Run(() => { var name = "bar"; });
    Run(() => { while (true) ; });
}

void Run(Action action)
{
    Console.WriteLine("action");
}

void Run(Func<Task> func) // Same behavior with Func<T> of any type. 
{
    Console.WriteLine("func");
}

Вывод:

действие
FUNC

Итак, как это может быть? Есть ли причина для этого?

4b9b3361

Ответ 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>.