Подтвердить что ты не робот

Почему Func <T> неоднозначен с Func <IEnumerable <T>>?

Это заставило меня смутить, поэтому я подумал, что попрошу здесь в надежде, что гуру С# может объяснить это мне.

Почему этот код генерирует ошибку?

class Program
{
    static void Main(string[] args)
    {
        Foo(X); // the error is on this line
    }

    static String X() { return "Test"; }

    static void Foo(Func<IEnumerable<String>> x) { }
    static void Foo(Func<String> x) { }
}

Указанная ошибка:

Error
    1
    The call is ambiguous between the following methods or properties:
'ConsoleApplication1.Program.Foo(System.Func<System.Collections.Generic.IEnumerable<string>>)' and 'ConsoleApplication1.Program.Foo(System.Func<string>)'
    C:\Users\mabster\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs
    12
    13
    ConsoleApplication1

Не имеет значения, какой тип я использую - если вы замените объявления String на "int" в этом коде, вы получите такую ​​же ошибку. Он, как компилятор, не может отличить между Func<T> и Func<IEnumerable<T>>.

Может кто-то пролить свет на это?

4b9b3361

Ответ 1

ОК, здесь сделка.

Краткая версия:

  • Ошибка двусмысленности, как ни странно, правильная.
  • Компилятор С# 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), мы выбираем версию Жирафа, потому что Жираф более специфичен, чем Животное. Мы знаем, что Жираф более конкретен, потому что каждый Жираф - это Животное, но не каждое Животное - Жираф.

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

Поэтому ни то, ни другое не лучше, поэтому ошибка перегрузки сообщает об ошибке.

Затем компилятор С# 4 имеет то, что кажется ошибкой, где его режим восстановления ошибок в любом случае выбирает одного из кандидатов и сообщает о другой ошибке. Мне непонятно, почему это происходит. В основном это говорит о том, что восстановление ошибок выбирает перегрузку IEnumerable, а затем замечает, что преобразование группы методов приводит к несостоятельному результату; а именно, что строка не совместима с IEnumerable<String>.

Вся ситуация довольно неудачна; возможно, было бы лучше сказать, что преобразование метода-группы-делегирования отсутствует, если типы возврата не совпадают. (Или, что преобразование, которое производит ошибку, всегда хуже, чем конверсия, которая этого не делает.) Однако мы застряли с этим сейчас.

Интересный факт: правила преобразования для lambdas учитывают возвращаемые типы. Если вы скажете Foo(()=>X()), тогда мы поступим правильно. Тот факт, что группы lambdas и группы методов имеют разные правила конвертирования, является довольно неудачным.

Итак, суммируя, компилятор на самом деле является правильной реализацией спецификации в этом случае, и этот конкретный сценарий является непреднамеренным последствием некоторых, возможно, неудачных вариантов выбора.

Ответ 2

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

Несмотря на то, что у вас есть только один метод с именем X, правила компилятора создаются для случая, когда их несколько.

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

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

Если вы выполняете часть работы вручную, вы решите проблему. например

Func<string> d = X;
Foo(d);

должен компилироваться просто отлично.