Params перегружает кажущуюся двусмысленность - все еще компилируется и работает?
Мы только что нашли их в нашем коде:
public static class ObjectContextExtensions
{
public static T Find<T>(this ObjectSet<T> set, int id, params Expression<Func<T, object>>[] includes) where T : class
{
...
}
public static T Find<T>(this ObjectSet<T> set, int id, params string[] includes) where T : class
{
...
}
}
Как вы можете видеть, они имеют одну и ту же подпись, кроме params
.
И они используются несколькими способами, один из них:
DBContext.Users.Find(userid.Value); //userid being an int? (Nullable<int>)
который, как ни странно, для меня, разрешает первую перегрузку.
Q1: Почему это не приводит к ошибке компиляции?
Q2: Почему компилятор С# разрешает вышеупомянутый вызов первому методу?
Изменить. Просто уточнить, это С# 4.0,.Net 4.0, Visual Studio 2010.
Ответы
Ответ 1
Это явно ошибка в разрешении перегрузки.
Он воспроизводится в С# 5 и С# 3, но не в Roslyn; Я не помню, решили ли мы преднамеренно принять разрушительное изменение или если это случайность. (У меня нет С# 4 на моей машине прямо сейчас, но если он будет воспроизводиться в 3 и 5, то он будет в 4 тоже почти наверняка.)
Я довел его до сведения моих бывших коллег по команде Roslyn. Если они вернутся ко мне с чем-нибудь интересным, я обновлю этот ответ.
Поскольку у меня больше нет доступа к исходному коду С# 3/4/5, я не могу сказать, в чем причина ошибки. Подумайте о том, как сообщить об этом на сайте connect.microsoft.com.
Здесь значительно упрощается воспроизведение:
class P
{
static void M(params System.Collections.Generic.List<string>[] p) {}
static void M(params int[] p) {}
static void Main()
{
M();
}
}
Похоже, что это связано с универсальностью типа элемента. Как ни странно, как указывает Крис в своем ответе, компилятор выбирает более общий характер! Я ожидал, что ошибка будет другой, и выберите менее общий.
Ошибка, кстати, скорее всего, моя ошибка, так как я выполнил довольно много работы над алгоритмом разрешения перегрузки в С# 3. Извиняюсь за ошибку.
UPDATE
Мои шпионы в команде Roslyn говорят мне, что это известная ошибка, давно стоящая в разрешении перегрузки. Было реализовано правило тай-брейка, которое никогда не было документировано или обосновано, в котором говорилось, что тип с большей общей арностью был лучшим типом. Это странное правило без оправдания, но оно никогда не удалялось из продукта. Команда Roslyn решила некоторое время назад принять взломное изменение и зафиксировать разрешение перегрузки, чтобы в этом случае возникла ошибка. (Я не помню этого решения, но мы приняли множество решений по этому поводу!)
Ответ 2
В IDEONE компилятор успешно создает ошибку. И это должно быть ошибкой, если вы шаг за шагом анализируете алгоритм разрешения:
1) Создан набор методов-кандидатов для вызова метода. Начиная с набора методов, связанных с M, которые были найдены предыдущим поиском элемента [...]. Сложная редукция состоит из применения следующих правил к каждому методу TN в множестве, где T - тип, в котором метод N объявляется:
Для простоты мы можем здесь вывести, что множество методов здесь содержит оба ваших метода.
Тогда редукция продолжается:
2) Если N не применимо относительно A (Раздел 7.4.2.1), тогда N удаляется из набора.
Оба метода применимы в отношении Правила применимых функций в их расширенной форме:
Развернутая форма строится путем замены массива параметров в объявлении члена функции нулевыми или более параметрами значения типа элемента массива параметров таким образом, чтобы количество аргументов в списке аргументов A соответствовало общему числу параметров. Если A имеет меньше аргументов, чем число фиксированных параметров в объявлении члена функции, расширенная форма члена функции не может быть построена и, следовательно, неприменима.
Это правило оставляет оба метода в наборе редукции.
Эксперименты (изменение типа параметра id
на float
в одном или обоих методах) подтверждают, что обе функции остаются в наборе кандидатов и далее различаются неявные правила сравнения конверсий.
Это говорит о том, что описанный выше алгоритм отлично работает с точки зрения создания набора кандидатов и не зависит от некоторого внутреннего упорядочения методов. Так как единственное, что отличается методами далее, Правила разрешения перегрузки , это кажется ошибкой, потому что:
лучший член функции - это один член функции, который лучше всех других членов функции относительно данного списка аргументов, при условии, что каждый член функции сравнивается со всеми другими членами функции, используя правила в Раздел 7.4.2.2.
и, очевидно, ни один из этих методов не лучше других, потому что здесь не существует неявных преобразований.
Ответ 3
Это не полный ответ, поскольку он объясняет различия, но не почему. Для полноты действительно нужна спецификация спецификации. Однако я не хотел, чтобы исследование, которое я сделал, чтобы потеряться в комментариях, я отправляю в качестве ответа.
Разница между двумя перегрузками заключается в том, что параметры для одного являются общими, а другие - нет. Компилятор, похоже, решил, что общий тип ближе, чем не общий.
То есть, если тип Expression<...>
был изменен на int
, компилятор будет жаловаться на двусмысленность. Подобно, если типы являются общими, тогда он жалуется на двусмысленность.
Следующий фрагмент будет демонстрировать это поведение более просто:
void Main()
{
TestMethod();
}
public void TestMethod(params string[] args)
{
Console.WriteLine("NonGeneric");
}
public void TestMethod(params List<string>[] args)
{
Console.WriteLine("Generic");
}
Это напечатает "Generic".