Порядок разрешения метода - PullRequest
10 голосов
/ 29 января 2010

Предположим, у нас есть:

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) !!

вызовет перезапись (десятичную). Зачем? И как я могу назвать Write (int) или Write (byte)?

Ответы [ 3 ]

18 голосов
/ 29 января 2010

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

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

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

Теперь 5 можно преобразовать в decimal (как и 5 после того, как оно было специально приведено к byte), поэтому 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
    }
}

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

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

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

7 голосов
/ 29 января 2010

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

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

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

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

6 голосов
/ 30 января 2010

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

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

...