Прежде всего, отмечу, что это дубликат:
Почему Func неоднозначен с Func?
В чем здесь проблема?
Предположение Томаса, по сути, верно.Вот точные детали.
Давайте рассмотрим это шаг за шагом.У нас есть вызов:
"test".Select<char, Tuple<char>>(Tuple.Create);
Разрешение перегрузки должно определять смысл вызова Select.Не существует метода «Выбор» для строки или какого-либо базового класса строки, поэтому это должен быть метод расширения.
Существует множество возможных методов расширения для набора кандидатов, поскольку строка преобразуется в IEnumerable<char>
и, вероятно, где-то там есть using System.Linq;
.Существует много методов расширения, которые соответствуют шаблону «Выбор, общая арность два, принимает IEnumerable<char>
в качестве первого аргумента при построении с заданными аргументами типа метода».
В частности, два из кандидаты :
Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,Tuple<char>>)
Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,int,Tuple<char>>)
Итак, первый вопрос, с которым мы сталкиваемся, это кандидаты применимо ?То есть существует ли неявное преобразование из каждого предоставленного аргумента в соответствующий тип формального параметра?
Отличный вопрос.Понятно, что первым аргументом будет «получатель», строка, и она будет неявно преобразована в IEnumerable<char>
.Теперь вопрос заключается в том, является ли второй аргумент, группа методов «Tuple.Create», неявно конвертируемым в формальные типы параметров Func<char,Tuple<char>>
и Func<char,int, Tuple<char>>
.
Когда группа методов конвертируется в данный делегаттип? Группа методов может быть преобразована в тип делегата, когда разрешение перегрузки могло бы быть успешным при наличии аргументов тех же типов, что и у формальных типов параметров делегата .
То есть M преобразуется в Func<A, R>
если бы разрешение перегрузки при вызове вида M(someA)
было бы успешным, учитывая выражение 'someA' типа 'A'.
Было бы успешным разрешение перегрузки при вызове Tuple.Create(someChar)
?Да;разрешение перегрузки выбрало бы Tuple.Create<char>(char)
.
Успешное разрешение перегрузки при вызове на Tuple.Create(someChar, someInt)
?Да, для разрешения перегрузки было бы выбрано Tuple.Create<char,int>(char, int)
.
Поскольку в обоих случаях разрешение перегрузки было бы успешным, группа методов может быть преобразована в оба типа делегатов. Тот факт, что возвращаемый тип одного из методов не соответствовал бы возвращаемому типу делегата, не имеет значения;Разрешение перегрузки не выполняется или не выполняется на основании анализа типа возврата .
Можно с полным основанием сказать, что конвертируемость из групп методов в типы делегатов должна быть успешной или неудачной на основании анализа возвращаемых типов, но это не тот язык, который указан;язык определен для использования разрешения перегрузки в качестве теста для преобразования группы методов, и я думаю, что это разумный выбор.
Поэтому у нас есть два подходящих кандидата.Есть ли способ, которым мы можем решить, который лучше , чем другой?В спецификации говорится, что преобразование в более конкретный тип лучше;если у вас есть
void M(string s) {}
void M(object o) {}
...
M(null);
, то разрешение перегрузки выбирает версию строки, потому что строка более конкретна, чем объект.Является ли один из этих типов делегатов более конкретным, чем другой?Нет. Ни один из них не является более конкретным, чем другой.(Это упрощение правил лучшего преобразования; на самом деле существует множество тай-брейков, но здесь ни одно из них не применимо.)
Поэтому нет оснований отдавать предпочтение одному над другим.
Опять же, можно разумно сказать, что, безусловно, есть основание, а именно, что одно из этих преобразований вызовет ошибку несоответствия возвращаемого типа делегата, а одно - нет.Опять же, тем не менее, язык задается для рассуждений о совершенстве, учитывая отношения между формальными типами параметров , а не о том, приведет ли выбранное преобразование в конечном итоге к ошибке.
Поскольку нет оснований отдавать предпочтение одному над другим, это ошибка неоднозначности.
Подобные ошибки неоднозначности легко построить. Например:
void M(Func<int, int> f){}
void M(Expression<Func<int, int>> ex) {}
...
M(x=>Q(++x));
Это неоднозначно. Даже если в дереве выражений ++ иметь недопустимое значение, логика конвертируемости не учитывает, есть ли внутри тела лямбда-выражения что-то недопустимое в дереве выражений . Логика преобразования просто гарантирует, что типы проверяются, и они делают. Учитывая это, нет оснований отдавать предпочтение одному из М над другим, так что это двусмысленность.
Вы заметили, что
"test".Select<char, Tuple<char>>(Tuple.Create<char>);
успешно. Теперь вы знаете, почему. Разрешение перегрузки должно определять, если
Tuple.Create<char>(someChar)
или
Tuple.Create<char>(someChar, someInt)
будет успешным. Поскольку первый делает, а второй нет, второй кандидат неприменим и исключен, и поэтому не может стать неоднозначным.
Вы также заметили, что
"test".Select<char, Tuple<char>>(x=>Tuple.Create(x));
однозначно. Лямбда-преобразования do учитывают совместимость типа возвращаемого выражения с типом возврата целевого делегата. К сожалению, группы методов и лямбда-выражения используют два слегка различающихся алгоритма для определения конвертируемости, но мы застряли с этим сейчас. Помните, преобразования групп методов были в языке намного дольше, чем лямбда-преобразования; если бы они были добавлены одновременно, я полагаю, что их правила были бы согласованы.