C# вызов метода интерфейса не виртуальной реализации

Я новичок в 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 ответа

Решение

Так как 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,

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

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

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

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");
    }
}

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам