C # метод переопределить разрешение странности - PullRequest
0 голосов
/ 05 октября 2018

Рассмотрим следующий фрагмент кода:

using System;

class Base
{
    public virtual void Foo(int x)
    {
        Console.WriteLine("Base.Foo(int)");
    }
}

class Derived : Base
{
    public override void Foo(int x)
    {
        Console.WriteLine("Derived.Foo(int)");
    }

    public void Foo(object o)
    {
        Console.WriteLine("Derived.Foo(object)");
    }
}

public class Program
{
    public static void Main()
    {
        Derived d = new Derived();
        int i = 10;
        d.Foo(i);
    }
}

И неожиданный вывод:

Derived.Foo(object)

Я ожидаю, что он выберет переопределенный метод Foo(int x), так как он болееконкретный.Однако компилятор C # выбирает ненаследуемую Foo(object o) версию.Это также вызывает операцию бокса.

В чем причина такого поведения?

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

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

Предположим, у нас есть базовый класс B и производный класс D. В B есть метод M, который принимает Giraffe.Теперь, помните, по предположению, автор D знает все о публичных и защищенных членах B .Другими словами, автор D должен знать на больше , чем автор B, потому что D был написан после B , а D был написан для расширения B до сценария, неуже обработано B .Поэтому мы должны полагать, что автор D выполняет лучше работу по реализации всех функциональных возможностей D, чем автор B.

Если автор D делаетперегрузка M, которая берет Животное, они говорят Я знаю лучше чем автор B, как обращаться с Животными, и это включает Жирафов .Мы должны ожидать разрешения перегрузки, когда при вызове DM (Giraffe) вызывается DM (Animal), а не BM (Giraffe).

Давайте скажем иначе: нам дано два возможных оправдания:

  • Звонок в DM (Жираф) должен идти к BM (Жираф), потому что Жираф более специфичен, чем Животное
  • Звонок в DM (Жираф) должен идти к DM (Животное), потому что Dболее конкретен, чем B

Оба обоснования имеют отношение специфичность , так какое же из этих обоснований лучше? Мы не вызываем никаких методов для Animal !Мы вызываем метод на D, поэтому , чтобы победила специфика .Специфика приемника намного, гораздо важнее, чем специфичность любого из его параметров. Существуют типы параметров для разрыва связи .Важно убедиться, что мы выбрали самый конкретный получатель , потому что этот метод был написан позже кем-то, кто больше знает сценарий, который D должен обрабатывать .

Теперь вы можете сказать, что, если автор D также переопределил BM (Жираф)?Есть два аргумента, почему вызов DM (Жираф) должен вызывать DM (Животное) в этом случае.

Первый , автор D должен знать, что DM (Животное) можно вызвать с Жирафом , и это должно быть написано, чтобы сделать правильную вещь .Таким образом, с точки зрения пользователя не должно иметь значения, разрешен ли вызов в DM (Animal) или BM (Giraffe), потому что D был написан правильно, чтобы делать правильные вещи.

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

0 голосов
/ 05 октября 2018

Это правило, и оно вам может не понравиться ...

Цитата Эрик Липперт

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

Причинаэто потому, что метод (то есть лучшее совпадение сигнатур) мог быть добавлен в более поздней версии и, таким образом, вводил " хрупкий базовый класс " сбой


Примечание : Это довольно сложная / углубленная часть спецификаций C #, и она прыгает повсюду.Тем не менее, основные части проблемы, с которой вы столкнулись, написаны следующим образом:

Обновление

И именно поэтому мне нравится stackoverflow, это такое прекрасное место для изучения.

Я цитировал раздел обработки во время выполнения вызова метода .Где, поскольку речь идет о разрешении перегрузки времени компиляции , и должно быть.

7.6.5.1 Вызовы методов

...

Набор методов-кандидатов сокращен и содержит только методы из наиболее производных типов: для каждого метода CF в наборе, где C - тип, в котором объявлен метод F, все методы объявленыв базовом виде C удаляются из набора .Кроме того, если C является типом класса, отличным от объекта, все методы, объявленные в типе интерфейса, удаляются из набора.(Это последнее правило действует только тогда, когда группа методов была результатом поиска члена для параметра типа, имеющего эффективный базовый класс, отличный от объекта, и непустой эффективный набор интерфейсов.)

Пожалуйстасм. ответ Эрика https://stackoverflow.com/a/52670391/1612975 для получения полной информации о том, что здесь происходит, и соответствующую часть спецификации

Оригинал

Спецификация языка C # Версия 5.0

7.5.5 Вызов члена функции

...

Обработка во время выполнения вызова члена функции состоит из следующегошаги, где M - член функции, а если M - член экземпляра, E - выражение экземпляра:

...

Если M - член функции экземпляра, объявленный в reference-тип:

  • E оценивается.Если эта оценка вызывает исключение, то дальнейшие шаги не выполняются.
  • Список аргументов оценивается, как описано в §7.5.1.
  • Если тип E является типом значения,преобразование бокса (§4.3.1) выполняется для преобразования E в объект типа, а E рассматривается как объект типа на следующих этапах.В этом случае M может быть только членом System.Object.
  • Значение E проверяется на действительность.Если значение E равно нулю, выдается исключение System.NullReferenceException и дальнейшие шаги не выполняются.
  • Определена реализация вызываемого элемента функции:
    • Если время привязкитип E - это интерфейс, вызываемый член функции - это реализация M, предоставляемая типом времени выполнения экземпляра, на который ссылается E .Этот член функции определяется путем применения правил отображения интерфейса (§13.4.4) для определения реализации M, предоставленной типом времени выполнения экземпляра, на который ссылается E.
    • В противном случае, если Mявляется членом виртуальной функции, вызываемый элемент функции является реализацией M, предоставляемой типом времени выполнения экземпляра, на который ссылается E. Этот член функции определяется путем применения правил для определения наиболее производной реализации (§10.6.3) M относительно типа времени выполнения экземпляра, на который ссылается E.
    • В противном случае M - это не виртуальный член функции, а вызываемым элементом функции является сам M.

После прочтения спецификаций, что интересно, если вы используете интерфейс, описывающий метод, компилятор выберет сигнатуру перегрузки, работая в свою очередь, как и ожидалось

  public interface ITest
  {
     void Foo(int x);
  }

Что может быть показаноздесь

Что касается интерфейса, то имеет смысл учитывать, что было реализовано поведение при перегрузке для защиты от хрупкого базового класса


Дополнительные ресурсы

Эрик Липперт, чем ближе, тем лучше

Аспект разрешения перегрузки в C #, о котором я хочу поговорить сегодня, действительно является фундаментальным правилом, согласно которому возможная перегрузкасчитается лучше, чем другой для данного сайта вызова: ближе всегда лучше, чем дальше.Есть несколько способов охарактеризовать «близость» в C #.Давайте начнем с самого близкого и продолжим наш путь:

  • Метод, впервые объявленный в производном классе, ближе, чем метод, впервые объявленный в базовом классе.
  • Метод вВложенный класс ближе, чем метод в содержащем классе.
  • Любой метод принимающего типа ближе, чем любой метод расширения.
  • Метод расширения, найденный в классе во вложенном пространстве имен, ближечем метод расширения, найденный в классе во внешнем пространстве имен.
  • Метод расширения, найденный в классе в текущем пространстве имен, ближе, чем метод расширения, найденный в классе в пространстве имен, упомянутом директивой using.
  • Метод расширения, найденный в классе в пространстве имен, упомянутом в директиве using, где директива находится во вложенном пространстве имен, ближе, чем метод расширения, найденный в классе в пространстве имен, упомянутом в директиве using, где директива имеет видво внешнем пространстве имен.
...