Чому додавання методу додасть неоднозначний виклик, якщо він не буде задіяний у неоднозначності


112

У мене цей клас

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)! Очевидно, що жоден із двох методів, що беруть участь у неоднозначності, не є нещодавно доданим методом, оскільки перший - це парами, а другий - загальний.

Це помилка? Яка частина специфікації говорить про те, що це має бути так?


2
Я не думаю, що це 'Overloaded.ComplexOverloadResolution(string)'стосується <string>(string)методу; Я думаю, що це стосується (string, object)методу, який не надається об'єктом.
фог

1
@phoog О, ці дані були скорочені StackOverflow, оскільки це тег, але повідомлення про помилку має позначення шаблону. Я додаю його ще раз.
Маккей

ти мене виловив! У своїй відповіді я цитував відповідні розділи специфікації, але останні півгодини я не провів їх читання та розуміння!
фог

@phoog, переглядаючи ці частини специфікації, я нічого не бачу в тому, щоб ввести неоднозначність в інші, крім себе, інші методи, а не два інші методи.
Маккей

Мені прийшло в голову, що це просто ножиці-папір : будь-який набір з двох різних значень має перемогу, але повний набір з трьох значень - ні.
фог

Відповіді:


107

Це помилка?

Так.

Вітаємо, ви знайшли помилку в роздільній здатності перевантаження. Клоп відтворюється в C # 4 і 5; він не відтворюється у "Рослінському" варіанті семантичного аналізатора. Я поінформував тестову команду 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. Отже, 1 не може бути переможцем.

Що краще, 2 або 3? Відповідний переломник полягає в тому, що загальний метод завжди гірший, ніж негенеричний метод. 2 гірше 3. Отже, 2 не можуть бути переможцями.

Для вибору з безлічі застосованих кандидатів кандидат повинен бути (1) непереможеним, (2) перемогти принаймні одного іншого кандидата і (3) бути єдиним кандидатом, який має перші два властивості. Кандидат трьох не бив жодного іншого кандидата, і б'є принаймні одного іншого кандидата; це єдиний кандидат з цим властивістю. Тому кандидат три - це єдиний найкращий кандидат . Це має виграти.

Мало того, що компілятор C # 4 помиляється, оскільки ви правильно зазначаєте, що він повідомляє про химерне повідомлення про помилку. Те, що компілятор неправильно аналізує роздільну здатність перевантаження, трохи дивно. Те, що це неправильне повідомлення про помилку, зовсім не дивно; евристична помилка "неоднозначного методу" в основному вибирає будь-які два методи з набору кандидатів, якщо кращого методу неможливо визначити. Не дуже добре знаходити "справжню" неоднозначність, якщо насправді вона є.

Можна з розумом запитати, чому це так. Це досить складно знайти два методу , які є «недвозначно неоднозначним» , тому що «betterness» ставлення неперехідне . Можна придумати ситуації, коли кандидат 1 кращий за 2, 2 кращий за 3, а 3 кращий за 1. У таких ситуаціях ми не можемо зробити кращого, ніж вибрати два з них як «неоднозначні».

Я хотів би покращити цю евристику для Росліна, але це низький пріоритет.

(Вправа для читача: "Розробити алгоритм лінійного часу, щоб визначити унікального найкращого члена набору з n елементів, де відношення кращості є неперехідним", було одним із запитань, що мені задавали в той день, коли я брав інтерв'ю для цієї команди. Це не так дуже важкий алгоритм; спробуйте.)

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

Якщо ви хочете ввести проблему Connect, щоб відстежити її, не соромтеся. Якщо ви просто хочете, щоб він був донесений до нас, вважайте це зробленим. Я продовжуватиму тестування наступного року.

Дякую за те, що ви звернули увагу на це Вибачте за помилку.


1
Дякуємо за відповідь. Ви сказали, що "1 гірший за 2", але він підбирає метод 1, якщо у мене просто є метод 1 і 2?
Маккей

@McKay: Ну, ти маєш рацію, я це сказав назад. Я виправлю текст.
Ерік Ліпперт

1
Дуже незручно читати фразу "решта року", враховуючи, що не залишилося навіть
півтижня:

2
@BoltClock дійсно, вислів "виїзд на решту року" передбачає один вихідний день.
фог

1
Я думаю так. Я читаю "3) бути єдиним кандидатом, який має перші два властивості", як "бути єдиним кандидатом, який (непереможений і б'є принаймні одного іншого кандидата)" . Але ваш останній коментар змушує мене думати "(будь єдиним кандидатом, який не переможений) і б'є принаймні одного іншого кандидата" . Англійська дійсно могла використовувати символи групування. Якщо остання правда, то я отримую це знову.
default.kramer

5

Яка частина специфікації говорить про те, що це має бути так?

Розділ 7.5.3 (роздільна здатність перевантаження), а також розділи 7.4 (пошук членів) та 7.5.2 (умовивід набору).

Особливо зверніть увагу на розділ 7.5.3.2 (кращий член функції), де в частині зазначено "необов'язкові параметри без відповідних аргументів вилучаються зі списку параметрів", а "Якщо M (p) - негенерований метод amd M (q) є загальний метод, тоді M (p) кращий, ніж M (q). "

Однак я не розумію цих частин специфікації досить ґрунтовно, щоб знати, які частини специфікації контролюють таку поведінку, не кажучи вже про те, чи відповідає вона.


Але це не пояснює, чому додавання члена може викликати неоднозначність між двома методами, які вже існували.
Маккей

@McKay досить справедливо (див. Редагування). Нам доведеться просто почекати, коли Ерік Ліпперт скаже нам, чи правильно це поведінка: ->
foog

1
Це правильні частини специфікації. Проблема в тому, що вони кажуть, що так не повинно бути!
Ерік Ліпперт

3

Ви можете уникнути цієї неоднозначності, змінивши ім'я першого параметра в деяких методах і вказавши параметр, який потрібно призначити

подобається це :

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");
    }
}

О, я знаю, що цей код поганий, і існує декілька способів його вирішення, питання було "чому компілятор поводиться таким чином?"
Маккей

1

Якщо ви видалите paramsсвій перший метод, цього не сталося. У першого та третього методу є обидва дійсні дзвінки ComplexOverloadResolution(string), але якщо ваш перший метод є, public void ComplexOverloadResolution(string[] something)то двозначності не буде.

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

Редагувати: Компілятор тут робить деякі шалені речі. Якщо ви перемістите ваш третій метод у коді після першого, він повідомить правильно. Тому здається, що потрібно взяти перші два перевантаження та повідомити про них, не перевіряючи правильність.

'ConsoleApplication1.Program.ComplexOverloadResolution (params string [])' та 'ConsoleApplication1.Program.ComplexOverloadResolution (рядок, об'єкт)'

Edit2: нова знахідка. Видалення будь-якого методу з вищевказаних трьох не призведе до неоднозначності між ними. Тож здається, що конфлікт виникає лише за наявності трьох методів незалежно від порядку.


Але це не пояснює, чому додавання члена може викликати неоднозначність між двома методами, які вже існували.
Маккей

Неоднозначність трапляється між вашим першим та третім методом, але чому компілятор повідомляє про інші два, це не в мені.
Томіслав Марковський

Але якщо я видаляю другий метод, у мене немає двозначності, він успішно викликає третій метод. Отже, схоже, що компілятор має неоднозначність між першим та третім методами.
Маккей

Дивіться мою редагування. Божевільні компілятори.
Томіслав Марковський

Насправді, будь-яке поєднання лише двох методів не створює двозначності. Це дуже дивно. Правка2.
Томіслав Марковський

1
  1. Якщо ти пишеш

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

    або просто написати

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

    це перетвориться на той самий метод , у метод

    public void ComplexOverloadResolution(params string[] something

    Це paramsключове слово причини, яке з нього найкраще відповідає також для випадку, коли не вказано жодного параметра

  2. Якщо ви спробуєте додати свій новий метод, як цей

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

    Він прекрасно компілюватиме та викликатиме цей метод, оскільки це ідеальне відповідність для вашого дзвінка з stringпараметром. Набагато сильніше тоді 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.

По-перше, ви пишете два випадки виклику, які однакові. Отже, звичайно, він піде в той же метод, чи ти мав на увазі написати щось інше?
Маккей

Але знову ж таки, якщо я правильно розумію решту вашої відповіді, ви не читаєте те, що говорить компілятор, - це плутанина, а саме між першим та другим методами, а не новим третім, який я щойно додав.
Маккей

ах, спасибі Але це все ще залишає проблему, про яку я згадую у своєму другому коментарі до вашого повідомлення.
Маккей

Щоб було зрозуміліше, ви заявляєте "Компілятор, стрибає в тотальній плутанині між першим методом і цим, щойно додав один". Але це не так. Це стрибки в замішанні на інші два методи. Метод парам та загальний метод.
Маккей

@McKay: ось, це стрибок у замішанні, маючи state3 функції, а не одну чи пару з них, якщо бути точним. Infact, достатньо прокоментувати будь-яку з них, щоб вирішити проблему. Найкраща відповідність серед доступних функцій - це одна з params, а друга - genericsпараметр, коли ми додаємо третю, вона створює плутанину в a setфункції. Я думаю, це, швидше за все, не чітке повідомлення про помилку, видане компілятором.
Тигран
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.