C # вызов метода интерфейса не виртуальной реализации - PullRequest
0 голосов
/ 06 сентября 2018

Я новичок в C # и не понимаю, почему компилятор не жалуется на этот код. Вот иерархия классов:

interface IAble
{
    void f();
}

class AAble : IAble
{
    public void f()
    {
        Debug.Log("---->> A - Able");
    }
}

class BAble : AAble
{
    public void f()
    {
        Debug.Log("---->> B - Able");
    }
}

код исполнения:

        IAble i = new BAble();
        i.f();

При исполнении ---->> A - Able было напечатано. Зачем? Как компилятор знает, какую функцию следует вызывать?

Когда принимается решение о том, какую функцию вызывать - время выполнения или время компиляции? Что если я оскверню новый класс class CAble : IAble?

Ответы [ 4 ]

0 голосов
/ 06 сентября 2018

Поскольку AAble реализует интерфейс IAble, его AAble.f помечен как реализация метода IAble.f для типа AAble.

BAble.f просто скрывает метод AAble.f, но не отменяет его.

IAble o = new BAble(); o.f(); // calls AAble.f
AAble o = new BAble(); o.f(); // calls AAble.f
BAble o = new BAble(); o.f(); // calls BAble.f
IAble o = new CAble(); o.f(); // calls CAble.f

Решение принимается во время компиляции:

// AAble.f in IL:
.method public final hidebysig newslot virtual 
    instance void f () cil managed 

// BAble.f in IL:
.method public hidebysig 
    instance void f () cil managed

Реализации интерфейса помечены как virtual в IL, хотя он не был помечен как виртуальный в C #. Метод также помечен как final в IL, если бы метод был virtual в C #, он не был бы помечен как final.

0 голосов
/ 06 сентября 2018

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

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

В более общем смысле: когда вы скрываете методы, то и версию метода, который будет использоваться, cmoes из класса, с которым вы его объявили.

Так что, если у вас был другой класс CAble и вы использовали как:

BAble c = new CAble();
b.f();

тогда результат будет ---->> B - Able.

В вашем случае вы объявляете переменную как IAble. Он не имеет реализации, поэтому он смотрит на реализацию, которая определена в классе AAble. Другие классы только скрывают метод.

Чтобы скрыть метод, вы можете указать оба метода с одинаковой сигнатурой. Но вы всегда должны использовать new keywrods, чтобы явно скрыть метод (который укажет, что скрытие было умышленно).

То, что вы ожидаете, это переопределение методов, которые выполняются с помощью override keywaord при определении метода.

Чтобы переопределить метод, он должен быть помечен как virtual (если он имеет реализацию) или abstract (если он не имеет реализации) в базовом классе.

0 голосов
/ 06 сентября 2018

Интерфейс должен быть реализован в классе, который наследуется от него напрямую, а не в одном из производных классов. Например, этот код не будет компилироваться:

class AAble : IAble
{
    public void f() { ... }
}

class BAble : AAble
{
    // An attempt to explicitly implement interface in BAble through AAble class
    void IAble.f()
    {
        Console.WriteLine("---->> B - Able");
    }
}

Когда мы передаем BAble в интерфейс IAble, используется реализация из AAble, поскольку это единственный класс в перспективе компиляции, который реализует интерфейс.

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

class BAble : AAble, IAble
{
    // Now it compiles
    void IAble.f()
    {
        Console.WriteLine("---->> B - Able");
    }
}

Выход: ---->> B - Able"

Или мы могли бы использовать полиморфизм. Это скажет компилятору всегда использовать переопределенную функцию:

class AAble : IAble
{
    public virtual void f()
    {
        Debug.Log("---->> A - Able");
    }
}

class BAble : AAble, IAble
{
    public override void f()
    {
        Console.WriteLine("---->> B - Able");
    }
}
0 голосов
/ 06 сентября 2018

Обычно компилятор предупреждает об этом, поскольку он скрывает метод. но в C # это допустимо для не виртуальных функций. Однако, конечно, если бы это была виртуальная функция, то, очевидно, B-версия метода работала бы.

Поскольку вы объявляете его как IAble, а он не является виртуальным, компилятор читает его как IAble. Если бы он был объявлен виртуальным, компилятор просканировал бы иерархию наследования и увидел бы, что его действительный класс - BAble, и он запустил бы код BAble.

...