Решено опубликовать небольшие примеры кода, чтобы проиллюстрировать, когда компилятор C # отправляет инструкции "call" и "callvirt".
Итак, вот исходный код всех типов, которые я использовал:
public sealed class SealedClass
{
public void DoSmth()
{ }
}
public class ClassWithSealedMethod : ClassWithVirtualMethod
{
public sealed override void DoSmth()
{ }
}
public class ClassWithVirtualMethod
{
public virtual void DoSmth()
{ }
}
Также у меня есть один метод, который вызывает все методы DoSmth ():
public void Call()
{
SealedClass sc = new SealedClass();
sc.DoSmth();
ClassWithVirtualMethod cwcm = new ClassWithVirtualMethod();
cwcm.DoSmth();
ClassWithSealedMethod cwsm = new ClassWithSealedMethod();
cwsm.DoSmth();
}
Рассматривая метод "Call ()", можно сказать, что (теоретически) компилятор C # должен выдавать 2 инструкции "callvirt" и 1 "call", верно?
К сожалению, реальность немного другая - 3 "callvirt" -s:
.method public hidebysig instance void Call() cil managed
{
.maxstack 1
.locals init (
[0] class TestApp.SealedClasses.SealedClass sc,
[1] class TestApp.SealedClasses.ClassWithVirtualMethod cwcm,
[2] class TestApp.SealedClasses.ClassWithSealedMethod cwsm)
L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: callvirt instance void TestApp.SealedClasses.SealedClass::DoSmth()
L_000c: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor()
L_0011: stloc.1
L_0012: ldloc.1
L_0013: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_0018: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor()
L_001d: stloc.2
L_001e: ldloc.2
L_001f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_0024: ret
}
Причина довольно проста: среда выполнения должна проверять, не равен ли экземпляр типа нулю, прежде чем вызывать метод DoSmth ().
НО мы все еще можем написать наш код таким образом, чтобы компилятор C # мог генерировать оптимизированный код IL:
public void Call()
{
new SealedClass().DoSmth();
new ClassWithVirtualMethod().DoSmth();
new ClassWithSealedMethod().DoSmth();
}
Результат:
.method public hidebysig instance void Call() cil managed
{
.maxstack 8
L_0000: newobj instance void TestApp.SealedClasses.SealedClass::.ctor()
L_0005: call instance void TestApp.SealedClasses.SealedClass::DoSmth()
L_000a: newobj instance void TestApp.SealedClasses.ClassWithVirtualMethod::.ctor()
L_000f: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_0014: newobj instance void TestApp.SealedClasses.ClassWithSealedMethod::.ctor()
L_0019: callvirt instance void TestApp.SealedClasses.ClassWithVirtualMethod::DoSmth()
L_001e: ret
}
Если вы попытаетесь вызвать не виртуальный метод незапечатанного класса таким же образом, вы также получите инструкцию «call» вместо «callvirt»