Зачем добавлять метод добавить неоднозначный вызов, если он не будет связан с неоднозначностью
У меня есть этот класс
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 ответов
Это ошибка?
Да.
Поздравляем, вы нашли ошибку в разрешении перегрузки. Ошибка воспроизводится в 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. Таким образом, 1 не может быть победителем.
Что лучше, 2 или 3? Соответствующим фактором является то, что универсальный метод всегда хуже, чем неуниверсальный метод. 2 хуже, чем 3. Таким образом, 2 не может быть победителем.
Чтобы быть выбранным из набора из нескольких подходящих кандидатов, кандидат должен быть (1) непобежденным, (2) побеждать по меньшей мере еще одного кандидата и (3) быть уникальным кандидатом, который имеет первые два свойства. Третий кандидат не избит ни одним другим кандидатом и побеждает по крайней мере еще одного кандидата; это единственный кандидат с этим свойством. Поэтому третий кандидат - единственный лучший кандидат. Это должно победить.
Компилятор C# 4 не только ошибается, но вы правильно заметили, что он сообщает о странном сообщении об ошибке. То, что компилятор неправильно анализирует разрешение перегрузки, немного удивляет. То, что сообщение об ошибке ошибочно, совершенно неудивительно; эвристика ошибок "неоднозначный метод" в основном выбирает любые два метода из набора кандидатов, если лучший метод не может быть определен. Не очень хорошо найти "настоящую" двусмысленность, если на самом деле она есть.
Можно разумно спросить, почему это так. Довольно сложно найти два метода, которые "однозначно неоднозначны", потому что отношение "лучше" непереходно. Возможны ситуации, когда кандидат 1 лучше, чем 2, 2 - лучше, чем 3, а 3 - лучше, чем 1. В таких ситуациях мы не можем добиться большего успеха, чем выбор двух из них как "неоднозначных".
Я хотел бы улучшить эту эвристику для Roslyn, но это низкий приоритет.
(Упражнение для читателя: "Разработать алгоритм линейного времени для определения уникального наилучшего члена набора из n элементов, в которых отношение Betterness непереходно" был одним из вопросов, которые мне задавали в тот день, когда я брал интервью у этой команды. очень сложный алгоритм, попробуйте.)
Одной из причин, почему мы так долго откладывали добавление необязательных аргументов в C#, было количество сложных неоднозначных ситуаций, которые он вводит в алгоритм разрешения перегрузки; по-видимому, мы не поняли это правильно.
Если вы хотите ввести проблему Connect для отслеживания, не стесняйтесь. Если вы хотите, чтобы это было доведено до нашего сведения, считайте, что это сделано Я продолжу тестирование в следующем году.
Спасибо, что обратили на это мое внимание. Извиняюсь за ошибку.
Какая часть спецификации говорит, что это должно быть так?
Раздел 7.5.3 (разрешение перегрузки), наряду с разделами 7.4 (поиск членов) и 7.5.2 (вывод типа).
Обратите особое внимание на раздел 7.5.3.2 (лучший член функции), в котором говорится, что "необязательные параметры без соответствующих аргументов удаляются из списка параметров", и "Если 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");
}
}
Если ты пишешь
var blah = new Overloaded(); blah.ComplexOverloadResolution("Which wins?");
или просто напиши
var blah = new Overloaded(); blah.ComplexOverloadResolution();
это будет в конечном итоге в том же методе, в методе
public void ComplexOverloadResolution(params string[] something
Это причина
params
Ключевое слово, которое делает его лучше всего подходит для случая, когда не указан параметрЕсли вы попытаетесь добавить новый метод, как это
public void ComplexOverloadResolution(string something) { Console.WriteLine("Added Later"); }
Он отлично скомпилирует и вызовет этот метод, так как он идеально подходит для вашего вызова с
string
параметр. Тогда намного сильнееparams string[] something
,Вы объявляете второй метод, как вы сделали
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.
Если вы удалите params
с вашего первого метода этого не произойдет. У вас первый и третий метод имеют оба допустимых вызова ComplexOverloadResolution(string)
, но если ваш первый метод public void ComplexOverloadResolution(string[] something)
не будет никакой двусмысленности.
Предоставление значения для параметра object somethingElse = null
делает его необязательным параметром и, следовательно, его не нужно указывать при вызове этой перегрузки.
Редактировать: компилятор делает некоторые сумасшедшие вещи здесь. Если вы переместите третий метод в коде после первого, он сообщит правильно. Таким образом, кажется, что он принимает первые две перегрузки и сообщает о них, не проверяя правильную.
"ConsoleApplication1.Program.ComplexOverloadResolution(params string[])" и "ConsoleApplication1.Program.ComplexOverloadResolution (строка, объект)"
Edit2: новая находка. Удаление любого метода из вышеперечисленных трех не приведет к двусмысленности между ними. Таким образом, кажется, что конфликт возникает только при наличии трех методов, независимо от порядка.