Ответ 1
Поскольку алгоритм, описанный в спецификации С#, в этом случае не преуспевает. Давайте посмотрим на спецификацию, чтобы понять, почему это так.
Описание алгоритма длинное и сложное, поэтому я сильно сократил это.
Соответствующие типы, упомянутые в алгоритме, имеют для вас следующие значения:
-
Eᵢ
= анонимный lambda(Foo x) => (Bar y) => new Baz()
-
Tᵢ
= тип параметра (Func<T1, Func<T2, T3>>
) -
Xᵢ
= три общих типа параметров (T1
,T2
,T3
)
Во-первых, theres первая фаза, которая в вашем случае делает только одно:
7.5.2.1 Первая фаза
Для каждого из аргументов метода
Eᵢ
(в вашем случае, только один, лямбда):
- Если
Eᵢ
является анонимной функцией [это], вывод явного типа параметра (§7.5.2.7) производится отEᵢ
доTᵢ
- В противном случае [не относится]
- В противном случае [не относится]
- В противном случае для этого аргумента не делается никаких указаний.
Я пропустил подробности вывода явного типа параметра здесь; достаточно сказать, что для вызова G((Foo x) => (Bar y) => new Baz())
он указывает на то, что T1
= Foo
.
Затем наступает вторая фаза, которая эффективно представляет собой цикл, который пытается сузить тип каждого типового параметра, пока он не найдет все или не сдастся. Одна важная маркерная точка - последняя:
7.5.2.2 Вторая фаза
Вторая фаза выполняется следующим образом:
- [...]
- В противном случае для всех аргументов
Eᵢ
с соответствующим типом параметраTᵢ
, где выходные типы (§7.5.2.4) содержат переменные незафиксированного типаXj
, но входные типы (§7.5.2.3) не имеют выхода (§7.5.2.6) производится отEᵢ
доTᵢ
. Затем повторяется вторая фаза.[Переведено и применено к вашему делу, это означает:
- В противном случае, если тип возврата делегата (т.е.
Func<T2,T3>
) содержит еще неопределенную переменную типа (она делает), но ее типы параметров (т.е.T1
) не (они этого не делают, мы уже знаем, чтоT1
=Foo
), выводится вывод выходного типа (§7.5.2.6).]
Вывод выходного типа теперь выполняется следующим образом; опять же, актуальна только одна маркерная точка, на этот раз ее первая:
7.5.2.6 Выводы выходного типа
Вывод выходного типа производится из выражения
E
в типT
следующим образом:
- Если
E
является анонимной функцией [с указанным типом возвратаU
(§7.5.2.12) иT
является типом типа делегата или дерева выражений с типом возвратаTb
, то нижняя (§7.5.2.9) производится отU
доTb
.- В противном случае, [rest snipped]
"Предполагаемый тип возврата" U
- это анонимный lambda (Bar y) => new Baz()
и Tb
is Func<T2,T3>
. Вывод нижней границы.
Я не думаю, что мне нужно процитировать весь алгоритм нижнего предела вывода (его длинный); достаточно сказать, что он не упоминает анонимные функции. Он заботится о взаимоотношениях наследования, реализации интерфейса, ковариации массивов, интерфейсе и делегировании co/contravariance,... но не lambdas. Следовательно, применяется его последняя маркерная точка:
- В противном случае никаких выводов не делается.
Затем мы возвращаемся ко второй фазе, которая отказывается, потому что не было сделано никаких указаний для T2
и T3
.
Мораль истории: алгоритм вывода типа не рекурсивен с lambdas. Он может только выводить типы из параметра и возвращать типы внешней лямбды, а не лямбда внутри. Только нижний вывод является рекурсивным (так что он может принимать вложенные общие конструкции, такие как List<Tuple<List<T1>, T2>>
в отдельности), но ни вывода выходных типов (§7.5.2.6), ни явные выражения типа параметра (§7.5.2.7) не являются рекурсивными и никогда не применяются к внутренним лямбдам.
Добавление
Когда вы добавляете вызов этой функции идентификации I
:
-
G((Foo x) => I((Bar y) => new Baz()));
тогда вывод типа сначала применяется к вызову I
, что приводит к тому, что тип возврата I
s выводится как Func<Bar, Baz>
. Тогда "предполагаемый тип возврата" U
внешней лямбда является типом делегирования Func<Bar, Baz>
и Tb
равен Func<T2, T3>
. Таким образом, вывод с нижней границей будет успешным, поскольку он будет сталкиваться с двумя явными типами делегатов (Func<Bar, Baz>
и Func<T2, T3>
), но без анонимных функций /lambdas. Вот почему функция идентификации делает ее успешной.