Подтвердить что ты не робот

Как метод скрывает работу в С#? (Часть вторая)

Следующая программа печатает

A:C(A,B)
B:C(A,B)

(как и должно быть)

public interface I
{
    string A();
}

public class C : I
{
    public string A()
    {
        return "A";
    }

    public string B()
    {
        return "B";
    }
}

public class A
{
    public virtual void Print(C c)
    {
        Console.WriteLine("A:C(" + c.A() + "," + c.B() + ")");
    }
}

public class B : A
{
    public new void Print(C c)
    {
        Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");
    }

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }
}

class Program
{
    public static void Main(string[] args)
    {
        A a = new A();
        B b = new B();
        C c = new C();
        a.Print(c);
        b.Print(c);
    }
}

однако, если я заменю ключевое слово 'new' на 'override' в классе B следующим образом:

    public override void Print(C c)

внезапная программа начинает печатать:

A:C(A,B)
B:I(A)

Почему?

4b9b3361

Ответ 1

Это связано с тем, как разрешены перегруженные методы.

Эффективно (упрощенно), компилятор сначала рассматривает объявленный тип выражения (B) в этом случае и ищет методы-кандидаты, которые сначала объявляются в этом типе. Если есть подходящие методы (т.е. Где все аргументы могут быть преобразованы в типы параметров метода), то он не смотрит ни на какие родительские типы. Это означает, что переопределенные методы, в которых начальное объявление находится в родительском типе, не получают внешний вид, если в производном типе есть какие-либо "только что объявленные" соответствующие методы.

Вот несколько более простой пример:

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(double d)
    {
        Console.WriteLine("Derived.Foo(double)");
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        d.Foo(10);
    }
}

Это печатает Derived.Foo(double) - хотя компилятор знает, что существует метод сопоставления с параметром типа int, а аргумент - type int, а преобразование из int в int - это "лучше", чем преобразование из int в double, тот факт, что только метод Foo(double) изначально объявлен в Derived означает, что компилятор игнорирует Foo(int).

Это очень удивительно ИМО. Я могу понять, почему это было бы так, если бы Derived не переопределял Foo - иначе введение нового, более конкретного метода в базовом классе может неожиданно изменить поведение - но ясно, что Derived здесь знает о Base.Foo(int) как он ее переопределяет. Это один из (относительно немного) пунктов, где я считаю, что разработчики С# приняли неправильное решение.

Ответ 2

Итак,

    public new void Print(C c)
    {
        Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");
    }

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }

Это объявляет новый метод печати. Теперь, поскольку B наследуется от A, вы просто вызываете новый метод дважды. Когда вы переопределяете метод, это затем меняет подпись метода при вызове A, но когда вы вызываете B-подпись, тогда у нее есть своя подпись метода.

Я не уверен, объясняю ли я ясный, но хороший вопрос.

с помощью нового:

A и B получают ту же реализацию метода печати. ​​

с использованием переопределения:

A имеет другую сигнатуру метода для B как, вы не изменили сигнатуру метода в B только в A.

с помощью нового он в основном игнорирует это:

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }

Ответ 3

Это был отличный вопрос.
Все ответы можно найти здесь: http://msdn.microsoft.com/en-us/library/6fawty39(VS.80).aspx

Суть в этом:

... компилятор С# сначала попытается сделать вызов, совместимый с версиями из [functionName], объявленного первоначально на [производный класс]. Методы переопределения не считается объявленным на классе, они являются новыми реализациями метод, объявленный в базовом классе. Только если компилятор С# не может вызов метода к исходному методу [Derived class] попытается выполнить вызов к переопределенному методу с тем же имя и совместимые параметры.

Так как у вас есть новый метод Print (I i) для производного класса, который соответствует аргументу "c" (поскольку c реализует I), этот метод имеет приоритет над методом "переопределения".

Когда вы отмечаете метод как "новый", оба они считаются реализованными в производном классе, а метод Print (C c) более точно соответствует параметру "c", поэтому он имеет приоритет.

Ответ 4

Это, по крайней мере, вопрос о том, как работает перегрузка методов на С#. Я думаю, вы выделили интересную ситуацию здесь...

В первом случае (с использованием ключевого слова new в методе) компилятор решает использовать перегрузку метода Print с параметром типа C, потому что он точно эквивалентен параметру переданного параметра (т.е. не требуется неявное преобразование), тогда как неявное преобразование в интерфейс мне понадобится, если компилятор должен выбрать метод Print, который принимает аргумент типа я - другими словами, он выбирает более "очевидную" перегрузку метода.

Во втором случае (используя ключевое слово override в методе) компилятор решает использовать перегрузку Print с параметром типа I, потому что, хотя вы переопределяете перегрузку метода Print(C c) в классе B, он эффективно определен в родительском классе A, что делает метод Print(I i) перегружать на самом деле перегрузку самого высокого уровня и, следовательно, самый прямой, то есть первый, который находит компилятор.

Надеюсь, это поможет вам понять. Дайте мне знать, если мне нужно еще прикрепить любые пункты...

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