Ответ 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. Вот почему функция идентификации делает ее успешной.