Почему компилятор C # не считает этот вывод универсального типа неоднозначным? - PullRequest
0 голосов
/ 18 мая 2018

Учитывая следующий класс:

public static class EnumHelper
{
    //Overload 1
    public static List<string> GetStrings<TEnum>(TEnum value)
    {
        return EnumHelper<TEnum>.GetStrings(value);
    }

    //Overload 2
    public static List<string> GetStrings<TEnum>(IEnumerable<TEnum> value)
    {
        return EnumHelper<TEnum>.GetStrings(value);
    }
}

Какие правила применяются для выбора одного из двух его универсальных методов?Например, в следующем коде:

List<MyEnum> list;
EnumHelper.GetStrings(list);

он заканчивает тем, что вызывает EnumHelper.GetStrings<List<MyEnum>>(List<MyEnum>) (т.е. перегрузка 1), даже если кажется, что вызов EnumHelper.GetStrings<MyEnum>(IEnumerable<MyEnum>) (то есть перегрузка 2) кажется таким же правильным.

Например, если я полностью удаляю перегрузку 1, то вызов все равно прекрасно компилируется, вместо этого выбирая метод, помеченный как перегрузка 2. Это, кажется, делает вывод обобщенного типа опасным, так как он вызывал метод, который кажется интуитивно понятнымкак хуже матча.Я передаю List / Enumerable как тип, который кажется очень специфичным и выглядит так, как будто он должен соответствовать методу с аналогичным параметром (IEnumerable<TEnum>), но он выбирает метод с более общим, универсальным параметром (TEnum value).

1 Ответ

0 голосов
/ 18 мая 2018

Какие правила применяются для выбора одного из двух его общих методов?

Правила в спецификации, которые, к сожалению, чрезвычайно сложны.В ECMA C # 5 стандарте соответствующий бит начинается в разделе 12.6.4.3 («лучший функциональный элемент»).

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

  • Для метода 1 TEnum подразумевается как List<MyEnum>
  • ДляМетод 2, TEnum выводится как MyEnum

Затем компилятор начинает проверять преобразования из аргументов в параметры, чтобы увидеть, является ли одно преобразование "лучше", чем другое.Это рассматривается в разделе 12.6.4.4 («лучшее преобразование из выражения»).

На данный момент мы рассматриваем следующие преобразования:

  • Перегрузка 1: List<MyEnum> в List<MyEnum> (поскольку TEnum определяется как List<MyEnum>)
  • Перегрузка 2: List<MyEnum> до IEnumerable<MyEnum> (поскольку TEnum подразумевается как MyEnum)

К счастью, здесь помогает первое правило:

При неявном преобразовании C1, которое преобразуется из выражения E в тип T1, и неявном преобразовании C2, которое преобразуется из выражения E втип T2, C1 лучше, чем C2, если выполняется хотя бы одно из следующих условий:

  • E имеет тип S и существует преобразование идентификаторов из S в T1, но не из S в T2

Там - это преобразование идентичности из List<MyEnum> в List<MyEnum>, но не преобразование удостоверения из List<MyEnum> в IEnumerable<MyEnum>, поэтому первое преобразование лучше.

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

Ваш аргумент о «более общих» и «более конкретных» параметрах был бы действительным, если бы эта более ранняя фаза закончилась в тай-брейке, но это не так:«лучшее преобразование» аргументов в параметры рассматривается раньше, чем «более конкретные параметры».

В общем, разрешение обеих перегрузок невероятно сложно.Он должен учитывать наследование, универсальные параметры, аргументы без типа (например, нулевой литерал, литерал по умолчанию, анонимные функции), массивы параметров, все возможные преобразования.Практически каждый раз, когда в C # добавляется новая функция, она влияет на разрешение перегрузки: (

...