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

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.

4b9b3361

Ответ 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".