Почему С# не может определить тип аргумента универсального типа из сигнатуры неуниверсального статического метода?

Я провел следующие тесты на вывод:

static class InferenceTest {
    static void TakeInt(int a) { }
    static int GiveInt() { return 0; }
    static int TakeAndGiveInt(int a) { return 0; }

    static void ConsumeAction1<T>(Action<T> a) { }
    static void ConsumeFunc1<T>(Func<T> f) { }
    static void ConsumeFunc2a<T1, T2>(Func<T1, T2> f) { }
    static void ConsumeFunc2b<T>(Func<int, T> f) { }
    static void ConsumeFunc2c<T>(Func<T, T> f) { }
    static void ConsumeFunc1Func2<T1, T2>(Func<T1> f1, Func<T1, T2> f2) { }

    static void Main() {
        ConsumeAction1(TakeInt);        //error
        ConsumeFunc1(GiveInt);          //ok
        ConsumeFunc2a(TakeAndGiveInt);  //error
        ConsumeFunc2b(TakeAndGiveInt);  //ok
        ConsumeFunc2c(TakeAndGiveInt);  //error
        ConsumeFunc1Func2(GiveInt, TakeAndGiveInt); //ok
    }
}

Результаты показывают, что компилятор С# не может вывести аргументы универсального типа для параметров функции делегата из неуниверсальной группы методов.

Больше всего меня озадачивает то, что С# может определить аргументы типа для Func<T1, T2> из значений, возвращаемых методом в ConsumeFunc1Func2, но не может определить типы для Func<T, T> в ConsumeFunc2c.

Этот вопрос похож на T Func & lt; S, T & gt; выводится из вывода лямбда-выражения, только когда S и T различаются? вопрос, но вместо лямбда-выражений с неизвестными типами параметров у нас есть неуниверсальные группы методов.

Тип Почему С# не может сделать вывод из этого, казалось бы, простого и очевидного вопроса, отвечает на вопросы "Почему не однозначных неуниверсальных методов недостаточно для вывода?" и "Почему существует разница между типами аргументов и типом возвращаемого значения для вывода?".

Вопросы:

Почему компилятор С# может определить тип Func<T>, используя тип возвращаемого значения, но не видит успеха в случае Func<T, T>?

Почему компилятор С# может вывести аргумент типа T1 для Func<T1, T2> из Func<T1> в ConsumeFunc1Func2, но не может вывести аргумент типа T для Func<T, T> из самого себя в ConsumeFunc2c, который кажется более простым?

Ответ 1

В общем случае имя метода не будет однозначно идентифицировать уникальный тип Action<T>, которому может быть присвоена группа методов. Например, даже если существует только одна перегрузка Fred и требуется один аргумент Cat, эта перегрузка может быть назначена не только на Action<Cat>, но и на некоторые другие типы, такие как Action<Mammal>, Action<Animal>, или Action<Object>. Хотя бывают случаи, когда одна замена типа во всех отношениях превосходит любую альтернативу, это не всегда так. Он более чист, чтобы определить язык, требующий указать тип делегата, чем пытаться "угадать" компилятор, тем более, что, поскольку угадай компилятор будет означать, что многие вещи, которые не должны нарушать изменения, будут (например, добавление перегрузки метода может сделать неоднозначный вывод типа, который использовался для работы).

Ответ 2

Параметры метода не проверяются.

Как и было предложено, в ConsumeFunc1Func2 компилятор выводит только из возвращаемых значений. В ConsumeFunc2c подпись TakeAndGiveInt не проверяется, не видит ли ее тип параметра метода тот же тип метода возвращаемого типа метода... параметры метода не проверяются!