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
(если он не имеет реализации) в базовом классе.