Почему выбирается этот универсальный метод вместо лучшего соответствия?

#c# #generics

Вопрос:

Я пытаюсь добавить этот метод расширения:

     public static bool Contains<T>(this IEnumerable<T> source, Nullable<T> value)
        where T : struct
    {
        if (value == null)
        {
            return false;
        }

        return source.Contains(value.Value);
    }
 

Однако он зацикливается и вызывает переполнение стека, потому что последняя строка приводит к обратному вызову этого метода расширения, а не System.Linq.Contains<T>(this IEnumerable<T> source, T value) метода, который, на мой взгляд, лучше подходит, поскольку аргумент Contains больше не является Nullable<T> прямым, а просто прямым T .

Я могу обойти это, переименовав свой метод расширения, но я хотел бы понять, что здесь происходит.

Комментарии:

1. @Charlieface: Я бы не решился ссылаться на VB.NET документы для вопроса на C#. В VB.NET правила, возможно, были настроены в соответствии с правилами C# (я не знаю, я не проверял), но правила разрешения перегрузки в любом случае не являются общими для языков .NET как таковых, поскольку это (в основном) работа компилятора, а не среды выполнения. Безусловно, оба VB.NET а спецификации C# для разрешения перегрузки достаточно сложны, поэтому с первого взгляда трудно сказать, одинаковы ли они во всех случаях, даже если они подходят для данного конкретного случая.

2. Извините, что это здесь docs.microsoft.com/en-us/dotnet/csharp/language-reference/… @JeroenMostert Вы совершенно правы, вот что происходит, когда вы прогугливаете очевидное разрешение перегрузки C# , выбираете первый вариант и не смотрите слишком внимательно.

Ответ №1:

Чтобы ответить на этот вопрос, нам нужно посмотреть, как компилятор разрешает методы расширения.

Глядя на спецификацию C# в MSDN (вы также можете увидеть это в ECMA-334 12.7.6.3):

если обычная обработка вызова не находит применимых методов, предпринимается попытка обработать конструкцию как вызов метода расширения.
….

Цель состоит в том C , чтобы найти лучшее имя типа, чтобы можно было выполнить соответствующий вызов статического метода:
….
Поиск C продолжается следующим образом:

  • Начиная с объявления ближайшего окружающего пространства имен[мой жирный шрифт], продолжая каждым объявлением окружающего пространства имен и заканчивая содержащим блоком компиляции, предпринимаются последовательные попытки найти подходящий набор методов расширения:
    • Если данное пространство имен или модуль компиляции напрямую содержит объявления неродовых типов Ci с подходящими методами расширения Mj , то набор этих методов расширения является набором кандидатов.
    • Если типы Ci , импортированные с помощью using_static_declarations и непосредственно объявленные в пространствах имен, импортированных с помощью using_namespace_directives в данном пространстве имен или блоке компиляции, напрямую содержат подходящие методы расширения Mj , то набор этих методов расширения является набором кандидатов.

      разрешение перегрузки применяется к набору кандидатов, как описано в (Разрешение перегрузки).
      C это тип, в котором лучший метод объявляется как метод расширения. [мой жирный шрифт]

Другими словами, рассматривается только один класс расширения, если в нем найден метод расширения, все остальные классы расширения игнорируются.

Подводя итог:

Предыдущие правила означают, что методы экземпляра имеют приоритет над расширением методов, что методы расширения существующих во внутреннем пространстве имен заявления имеют приоритет над расширением методов, доступных в космическом пространстве имен деклараций [мой смелый], и что методы расширения, объявленные непосредственно в пространстве имен имеют приоритет над расширением методов импортировать в том же пространстве имен с помощью директивы пространства имен.