Почему компилятор C# не может вывести параметры типа для возвращаемых значений?

у меня есть этот код (минимизированный для ясности):

interface IEither<out TL, out TR> {
}

class Left<TL, TR> : IEither<TL, TR> {
    public Left(TL value) { }
}

static class Either {
    public static IEither<TL, TR> Left<TL, TR> (this TL left) {
        return new Left<TL, TR> (left);
    }
}

почему я не могу сказать:

static class Foo
{
    public static IEither<string, int> Bar ()
    {
        //return "Hello".Left (); // Doesn't compile
        return "Hello".Left<string, int> (); // Compiles
    }
}

Я получаю сообщение об ошибке 'string' does not contain a definition for 'Left' and no extension method 'Left' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) (CS1061).

3 ответов


Я бы рекомендовал вам взглянуть на раздел 7.5.2 на C# спецификация.

7.5.2 определение типа

когда вызывается универсальный метод без указания аргументов типа, a вывод типа из аргументов только контекст вызова, который не разрешен во время разрешения универсального типа.


return "Hello".Left<string, int> (); // Compiles

никого не удивишь. Вы явно указали параметры типа, и компилятор доволен.

return "Hello".Left (); // Doesn't compile

неудивительно, что компилятор не имеет возможности узнать, что такое TR в этом случае. TL можно сделать вывод, потому что TL передается в качестве параметра left но TR не может.

компилятор C# не делает никаких предположений в том, что вы имели в виду, если намерение не ясно, это вызовет ошибку компилятора. Если компилятор думает, что вы можете сделать что-то неправильно дает предупреждение компилятора.


отказ от ответственности: этот ответ включает в себя угадывание.

информация о TR заключается в том, что он используется как выражение ребенка return, который, в свою очередь, может быть сопоставлено с IEither<Foo, Bar>, для получения информации, что TR is Bar.

но есть проблема. Когда компилятор имеет абстрактное синтаксическое дерево, легче начать с корневого и выводить типы выражений, разрешать перегрузки и т. д., постепенно перемещаясь к листьям дерево. Это проще всего сделать и что делается чаще.

ваш сценарий требует, чтобы компилятор работал полностью назад - от вызова метода к вызову конструктора к дочернему выражению return а затем проконсультироваться с объявлением текущего метода (и узнать, что правильная комбинация <string, int>, потому что любой другой не может скомпилировать). На первый взгляд, этого трудно достичь.

но для этого есть некоторый приоритет в C#: вы можете создать функцию (более высокого порядка), которая просто возвращает лямбду и тип вывода будет работа на основе декларации. Такие вещи также работают с lambdas при объявлении локальной переменной (вы не можете назначить их var и попросите компилятор вывести типы параметров из более позднего использования в области).

Итак, почему они не реализовали его для случая, который вы описываете, учитывая, что они сделали это с лямбдами?

  1. они had чтобы сделать это с lambdas: весь смысл лямбда - выражений заключается в том, что они не должны выглядеть как большое дело-они должны быть как можно проще писать (чтобы быстро выразить критерий фильтрации, критерий сортировки и т. д.). Это имеет ценность.
  2. несмотря на то, что это работает с лямбдами, это далеко не идеально - на самом деле, он может вести себя очень странно время от времени. Таким образом, это может рассматриваться дизайнерами как необходимое зло, которое не должно быть распространяется на остальной язык.
  3. ваш случай реже, и обходной путь прост.
  4. это может быть просто случай "никто не реализовал его пока".