Порядок разрешения метода

Предположим, что мы имеем:

public class FooBase
{
    public void Write(byte value)
    {
        //something
    }

    public void Write(int value)
    {
        //something
    }
}

public class Foo : FooBase
{
    public void Write(decimal value)
    {
        //something
    }
}

чем это:

        var writer = new Foo();

        writer.Write(5);         //calls Write(decimal) !!
        writer.Write((byte)6);   //calls Write(decimal) !!

назовет Запись (десятичное число) перегрузка. Почему? И как я могу назвать Запись (интервал) или Запись (байт)?

10
задан Tomáš Holan 29 January 2010 в 22:26
поделиться

3 ответа

Вы можете позвонить Write (Byte) , как это:

((FooBase)writer).Write((byte)6);

, как правило, C # предпочтит использовать перегрузку, определенную на типе напрямую и бросить свой аргумент, а не использовать Перегрузка, определенная на родительском типе.

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

7
ответ дан 3 December 2019 в 14:52
поделиться

Это довольно часто задаваемый вопрос. Анализ Джона, конечно же, верен. Для дальнейшего прочтения, вот моя статья на эту тему:

http://blogs.msdn.com/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

6
ответ дан 3 December 2019 в 14:52
поделиться

Да, это будет. По сути, это моя головоломка №1 . На самом деле это не вывод типа в том смысле, в котором он обычно используется, - это разрешение перегрузки. Вот где нужно искать в спец.

Теперь типом времени компиляции Writer является Foo .

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

Теперь число 5 может быть преобразовано в десятичное (равно как и 5 после того, как оно было специально преобразовано в байт ), поэтому Foo.Write (decimal) будет применимый функциональный член для вызова вашего метода - и это то, что вызывается. Он даже не рассматривает перегрузки FooBase.Write , потому что он уже нашел совпадение.

Пока это разумно - идея состоит в том, что добавление метода к базовому типу не должно изменять разрешение перегрузки для существующего кода, о котором дочерний тип не знает. Это немного падает, когда задействовано переопределение. Давайте немного изменим ваш код - я собираюсь удалить версию byte , сделать Write (int) виртуальным и переопределить его в Foo :

public class FooBase
{
    public virtual void Write(int value)
    {
        //something
    }
}

public class Foo : FooBase
{
    public override void Write(int value)
    {
        //something
    }

    public void Write(decimal value)
    {
        //something
    }
}

Теперь что будет делать new Foo (). Write (5) ? Он будет по-прежнему вызывать Foo.Write (decimal) - потому что Foo.Write (int) не объявлен в Foo, только переопределил там. Если вы измените override на new , тогда он будет вызван, потому что тогда это будет считаться объявлением нового метода.

Я думаю, что этот аспект противоречит здравому смыслу - и он не нужен для управления версиями, как будто вы переопределяете метод в дочернем классе, вы четко знаете, что он находится в базовом классе.

Мораль истории : постарайтесь не делать этого. В конечном итоге вы запутаете людей. Если вы унаследованы от класса, не добавляйте новые методы с тем же именем, но с другой подписью, если это возможно.

18
ответ дан 3 December 2019 в 14:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: