Зачем добавлять метод добавить неоднозначный вызов, если он не будет участвовать в неоднозначности


у меня есть этот класс

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] something)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }
}

если я назову это так:

        var blah = new Overloaded();
        blah.ComplexOverloadResolution("Which wins?");

он пишет Normal Winner в консоли.

но, если я добавлю еще один способ:

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }

Я получаю следующую ошибку:

вызов неоднозначен между следующих методов или свойств: > 'Overloaded.ComplexOverloadResolution(params string[])' и 'Overloaded.ComplexOverloadResolution<string>(string)'

Я могу понять, что добавление метода может ввести двусмысленность, но это неопределенность между двумя методами, которые уже существовали (params string[]) и <string>(string)! Очевидно, что ни один из двух методов, участвующих в двусмысленности, не является вновь добавленным методом, потому что первый-это params, а второй-общий.

это ошибка? Какая часть спецификации говорит, что это должно быть так?

5 110

5 ответов:

это ошибка?

да.

Поздравляем, вы нашли ошибку в разрешении перегрузки. Ошибка воспроизводится в C# 4 и 5; она не воспроизводится в версии семантического анализатора" Roslyn". Я проинформировал тестовую команду C# 5, и, надеюсь, мы сможем получить это расследование и решить до окончательного релиза. (Как всегда, никаких обещаний.)

правильный анализ следует. Кандидатами являются:

0: C(params string[]) in its normal form
1: C(params string[]) in its expanded form
2: C<string>(string) 
3: C(string, object) 

кандидат ноль, очевидно, неприменим, потому что string не конвертируется в string[]. Остается три.

из трех, мы должны определить уникальный лучший способ. Мы делаем это путем попарного сравнения трех оставшихся кандидатов. Таких пар три. Все они имеют одинаковых списки параметров после того, как мы уберем опущенные необязательные параметры, это означает, что мы должны перейти к расширенному раунду тай-брейка, описанному в разделе 7.5.3.2 спецификация.

что лучше, 1 или 2? Соответствующий тай-брейк заключается в том, что общий метод всегда хуже, чем неродовой метод. 2 хуже, чем 1. Так что 2 не может быть победителя.

что лучше, 1 или 3? Соответствующий тай-брейкер: метод, применимый только в его расширенной форме, всегда хуже, чем метод, применимый в его нормальной форме. Поэтому 1 хуже, чем 3. Так что я не могу быть победителем.

что лучше, 2 или 3? Соответствующие нужно что универсальный метод всегда хуже, чем не универсальный метод. 2 хуже, чем 3. Так что 2 не может быть победителя.

чтобы быть выбранным из набора нескольких применимых кандидатов, кандидат должен быть (1) непобедимым, (2) бить по крайней мере одного другого кандидата и (3) быть уникальным кандидатом, который имеет первые два свойства. Кандидат три не побежден никаким другим кандидатом и бьет по крайней мере одного другого кандидата; это единственный кандидат с этим свойством. Поэтому кандидат три уникальный лучший кандидат. Он должен победить.

не только компилятор C# 4 получает его неправильно, как вы правильно заметили, он сообщает странное сообщение об ошибке. То, что компилятор получает анализ разрешения перегрузки неправильно, немного удивительно. То, что он получает сообщение об ошибке неправильно, совершенно неудивительно; эвристика ошибки "неоднозначный метод" в основном выбирает любые два метода из набора кандидатов, если лучший метод не может быть определенный. Он не очень хорошо находит" реальную " двусмысленность, если она действительно существует.

можно было бы разумно спросить, почему это так. Это довольно сложно найти два методы, которые "однозначно неоднозначны", потому что отношение" лучше"непереходные. Можно придумать ситуации, когда кандидат 1 лучше, чем 2, 2 лучше, чем 3, и 3 лучше, чем 1. В таких ситуациях мы не можем сделать лучше, чем собирать две из них, как " неоднозначные".

Я хотел бы улучшить эту эвристику для Roslyn, но это низкий приоритет.

(упражнение для читателя: "разработать алгоритм линейного времени для определения уникального лучшего члена набора из n элементов, где отношение улучшения непереходно" было одним из вопросов, которые мне задали в тот день, когда я брал интервью для этой команды. Это не очень сложный алгоритм; дайте ему шанс.)

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

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

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

какая часть спецификации говорит, что это должно быть так?

раздел 7.5.3 (разрешение перегрузки), наряду с разделами 7.4 (поиск членов) и 7.5.2 (вывод типа).

обратите особое внимание на раздел 7.5.3.2 (better function member), в котором частично говорится: "дополнительные параметры без соответствующих аргументов удаляются из списка параметров" и "если M(p) является неродовым методом amd M(q) является общим методом, то M (p) лучше, чем M (q)."

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

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

такой :

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] somethings)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Overloaded a = new Overloaded();
        a.ComplexOverloadResolution(something:"asd");
    }
}

если убрать params с вашего первого метода этого не произойдет. Вы первый и третий метод имеют оба допустимых вызова ComplexOverloadResolution(string), но если ваш первый метод public void ComplexOverloadResolution(string[] something) не будет никакой двусмысленности.

предоставление значения для параметра object somethingElse = null делает его необязательным параметром, и поэтому его не нужно указывать при вызове этой перегрузки.

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

'.Программа.ComplexOverloadResolution (params string []) ' и 'ConsoleApplication1.Программа.ComplexOverloadResolution(строка, объект)'

Edit2: новая находка. Удаление любого метода из вышеуказанных трех не приведет к двусмысленности между ними. Поэтому кажется, что конфликт появляется только в том случае, если есть три метода присутствует, независимо от порядка.

  1. если вы пишите

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");
    

    или просто написать

    var blah = new Overloaded();
    blah.ComplexOverloadResolution();
    

    он заканчивается в тот же метод метод

    public void ComplexOverloadResolution(params string[] something
    

    это params ключевое слово, которое делает из него это лучше всего подходит также для случая, когда параметр не указан

  2. если вы попытаетесь добавить yuor новый метод, как это

    public void ComplexOverloadResolution(string something)
    {
        Console.WriteLine("Added Later");
    }
    

    он будет прекрасно компилировать и называть это метод, как это идеальный матч для вашего звонка с . Гораздо сильнее, чем params string[] something.

  3. вы объявляете второй метод, как вы сделали

    public void ComplexOverloadResolution(string something, object something=null);
    

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

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");
    

    Infact, если вы удалите строковый параметр из вызова, например, следующий код, все компилируется правильно и работает как раньше

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.