Выполнение виртуальных методов в запечатанных классах

Рейтинг: 3Ответов: 2Опубликовано: 13.03.2023

Рихтер говорит:

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

Как факт того, что у запечатанного класса не может быть производных классов, даёт право на невиртуальный вызов?

К примеру, если запечатанный класс наследует класс, в котором определён виртуальный метод, и его не переопределяет, то происходит поиск ближайшего переопределения вверх по иерархии с последующим полиморфным вызовом.

Или, к примеру, если запечатанный класс даже переопределяет виртуальный метод. Как факт того, что от этого класса не может быть производных классов, позволяет вызывать этот виртуальный метод невиртуально (посредством call)?

Я ещё как-то могу понять, когда говорят о значимых типах, к примеру о структурах, что компилятор знает, что полиморфизм здесь невозможен, так как структура является значимым типом, а значимые типы не могут применяться для другого типа в качестве базового и по-другому реализовывать виртуальный метод (так как нельзя унаследовать структуру).

Вот ещё кусок фразы:

Компиляторы стремятся использовать команду call при вызове методов, определённых значимыми типами, поскольку они запечатаны. В этом случае полиморфизм невозможен даже для виртуальных методов, и вызов выполняется быстрее.

class A
{
    public virtual void Show()
    {
        Console.WriteLine("Class A");
    }
}
 
sealed class B : A
{
}

public static void Main()
{
    B ob=new B();
    //Вызов метода Show из базового класса А выполняется что ли неполиморфно т.к. класс B запечатан?
    ob.Show();
}

Вопрос: Как факт того, что класс не может выступать в качестве базового типа для других типов, позволяет компилятору (или JIT-компилятору) вызывать виртуальные методы невиртуально? Что нам это даёт? Мы же не освобождаем компилятор от поиска других ближайших реализаций этого метода вверх по иерархии?

Поиск по иерархии происходит снизу вверх. Как факт того, что ниже ничего не будет, даёт нам право вызывать виртуальные методы невиртуально?

Ответы

▲ 4Принят

Если хотите знать, что думает об этом Microsoft - вот ссылка почитать. А так, степень оптимизации девиртуализации сильно зависит от версии .NET. Чем новее, тем эффективнее реализована девиртуализация.

А вообще если речь про .NET 7 и новее, то Microsoft пишут следующее:

But if the JIT can see that a virtual method is being invoked on a sealed type, it can devirtualize the call and potentially even inline it.

Вольный перевод: Если JIT видит, что виртуальный вызов происходит на запечатанном sealed классе, он может его девиртуализовать, или даже встроить в вызывающий код.

▲ 1

Попробую я объяснить, как сам понимаю.
Создадим следующие классы:

class A
{
    public virtual void Show() => Console.WriteLine("A");
}

class B : A
{
    public override void Show() => Console.WriteLine("B");
}

class C : B
{
    public override void Show() => Console.WriteLine("C");
}

sealed class D : A
{
    public override void Show() => Console.WriteLine("D");
}

Рассмотрим такой код:

B b1 = new B();
B b2 = new C();
D d = new D();

b1.Show();
b2.Show();
d.Show();

Классы B и D - прямые потомки A. На этом основании можно решить, что вызовы их методов Show должны быть одинаковы.

Однако, у класса B могут быть потомки, поэтому в ссылке типа B может храниться как экземпляр B, так и экземпляр C. Неизвестно заранее, что там будет. Поэтому вызов будет виртуальным.

В ссылке D может быть только экземпляр D. Поэтому можно вызвать его метод напрямую.