ОК, вот сделка.
Краткая версия:
- Ошибка неоднозначности, как ни странно, правильная.
- Компилятор C # 4 также выдает ложную ошибку после правильной ошибки неоднозначности. Это похоже на ошибку в компиляторе.
Длинная версия:
У нас проблема с разрешением перегрузки. Разрешение перегрузки очень точно указано.
Шаг первый: определить набор кандидатов. Это легко. Кандидатами являются Foo(Func<IEnumerable<String>>)
и Foo(Func<String>)
.
Шаг второй: определить, какие члены набора кандидатов являются применимыми . Применимый член имеет каждый аргумент, конвертируемый в каждый тип параметра.
Применимо ли Foo(Func<IEnumerable<String>>)
? Ну, X
конвертируется в Func<IEnumerable<String>
?
Мы обращаемся к разделу 6.6 спецификации. Эта часть спецификации - то, что мы, языковые дизайнеры, называем «действительно странным». В основном, это говорит, что преобразование может существовать, но использование этого преобразования является ошибкой . (Есть веские причины, по которым мы имеем эту странную ситуацию, в основном связанную с тем, чтобы избежать будущих переломных изменений и избежать ситуаций "курица и яйцо", но в вашем случае в результате мы получаем несколько неудачное поведение.)
По сути, правило здесь таково, что преобразование из X в тип делегата без параметров существует , если разрешение перегрузки при вызове вида X()
будет успешным. Очевидно, что такой вызов будет успешным, и, следовательно, преобразование существует. На самом деле с использованием это преобразование является ошибкой, поскольку возвращаемые типы не совпадают, но разрешение перегрузки всегда игнорирует возвращаемые типы .
Итак, существует преобразование из X
в Func<IEnumerable<String>
, и поэтому эта перегрузка является подходящим кандидатом.
Очевидно, что по той же причине другая перегрузка также является подходящим кандидатом.
Шаг третий: Теперь у нас есть два подходящих кандидата. Какой из них "лучше"?
Тот, который «лучше», - это тот, у кого более конкретный тип . Если у вас есть два подходящих кандидата, M(Animal)
и M(Giraffe)
, мы выбираем версию Giraffe, потому что Giraffe более специфичен, чем Animal. Мы знаем, что Жираф более конкретен, потому что каждый Жираф - Животное, но не каждое Животное - Жираф.
Но в вашем случае ни один тип не является более конкретным, чем другой. Нет преобразования между двумя типами Func.
Следовательно, ни то, ни другое лучше, поэтому разрешение перегрузки сообщает об ошибке.
Компилятор C # 4 затем имеет то, что кажется ошибкой, когда его режим восстановления после ошибки выбирает одного из кандидатов в любом случае и сообщает другую ошибку. Мне не понятно, почему это происходит. В основном это говорит о том, что для восстановления после ошибок выбирается перегрузка IEnumerable, а затем отмечается, что преобразование группы методов приводит к несостоятельному результату; а именно, эта строка не совместима с IEnumerable<String>
.
Вся ситуация довольно неудачная; могло бы быть лучше сказать, что нет преобразования метода группы в делегат, если возвращаемые типы не совпадают. (Или, что преобразование, которое приводит к ошибке, всегда хуже, чем преобразование, которое этого не делает.) Однако мы застряли с этим сейчас.
Интересный факт: правила конвертации лямбд do учитывают типы возврата. Если вы скажете Foo(()=>X())
, тогда мы поступим правильно. Тот факт, что лямбды и группы методов имеют разные правила конвертируемости, весьма прискорбен.
Итак, подытоживая, компилятор на самом деле является правильной реализацией спецификации в этом случае, и этот конкретный сценарий является непреднамеренным следствием некоторых, возможно, неудачных выборов спецификации.