Когда среда выполнения выполняет инструкцию call
, она вызывает точный фрагмент кода (метод). Там нет вопроса о том, где он существует. После того, как IL был JITted, полученный машинный код на сайте вызова является безусловной jmp
инструкцией.
Напротив, инструкция callvirt
используется для полиморфного вызова виртуальных методов. Точное местоположение кода метода должно быть определено во время выполнения для каждого вызова. Результирующий код JITted включает некоторую косвенность через структуры vtable. Следовательно, вызов выполняется медленнее, но он более гибкий, так как допускает полиморфные вызовы.
Обратите внимание, что компилятор может выдавать call
инструкции для виртуальных методов. Например:
sealed class SealedObject : object
{
public override bool Equals(object o)
{
// ...
}
}
Рассмотрим код вызова:
SealedObject a = // ...
object b = // ...
bool equal = a.Equals(b);
Хотя System.Object.Equals(object)
- это виртуальный метод, в этом использовании невозможно перегрузка метода Equals
. SealedObject
является запечатанным классом и не может иметь подклассов.
По этой причине классы .NET sealed
могут иметь лучшую производительность диспетчеризации методов, чем их незапечатанные аналоги.
РЕДАКТИРОВАТЬ: Оказывается, я был не прав. Компилятор C # не может сделать безусловный переход к местоположению метода, поскольку ссылка на объект (значение this
в методе) может быть нулевой. Вместо этого он генерирует callvirt
, который выполняет проверку на ноль и выдает, если требуется.
Это фактически объясняет некоторый странный код, который я нашел в .NET Framework с помощью Reflector:
if (this==null) // ...
Компилятор может выдавать проверяемый код с нулевым значением для указателя this
(local0), только csc этого не делает.
Так что, я думаю, call
используется только для статических методов и структур класса.
Учитывая эту информацию, мне кажется, что sealed
полезен только для безопасности API. Я нашел еще один вопрос , который, похоже, говорит о том, что нет никаких преимуществ в производительности для запечатывания ваших классов.
РЕДАКТИРОВАТЬ 2: Это больше, чем кажется. Например, следующий код выдает инструкцию call
:
new SealedObject().Equals("Rubber ducky");
Очевидно, что в таком случае нет шансов, что экземпляр объекта может быть нулевым.
Интересно, что в сборке DEBUG следующий код выдает callvirt
:
var o = new SealedObject();
o.Equals("Rubber ducky");
Это потому, что вы можете установить точку останова во второй строке и изменить значение o
. В релизных сборках я предполагаю, что вызов будет call
, а не callvirt
.
К сожалению, мой компьютер в данный момент не работает, но я поэкспериментирую с ним, как только он снова включится.