Почему компилятор 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
и попросите компилятор вывести типы параметров из более позднего использования в области).
Итак, почему они не реализовали его для случая, который вы описываете, учитывая, что они сделали это с лямбдами?
- они had чтобы сделать это с lambdas: весь смысл лямбда - выражений заключается в том, что они не должны выглядеть как большое дело-они должны быть как можно проще писать (чтобы быстро выразить критерий фильтрации, критерий сортировки и т. д.). Это имеет ценность.
- несмотря на то, что это работает с лямбдами, это далеко не идеально - на самом деле, он может вести себя очень странно время от времени. Таким образом, это может рассматриваться дизайнерами как необходимое зло, которое не должно быть распространяется на остальной язык.
- ваш случай реже, и обходной путь прост.
- это может быть просто случай "никто не реализовал его пока".