Ответ 1
Как я уже отмечал в своем вопросе, что эта запечатанная виртуальная аномалия фактически предусмотрена стандартом CIL. Остается неясным, почему в стандарте CIL конкретно упоминаются, что методы делегирования Invoke
, BeginInvoke
и EndInvoke
должны быть виртуальными, в то же время требуя печати наследуемого класса Delegate
.
Кроме того, после прохождения кода SSCLI я узнал, что внутренняя оптимизация JIT-компилятора автоматически переводит любой вызов callvirt
по виртуальному методу закрытого класса на обычный вызов с дополнительной проверкой нуля. Это означает, что делегаты не пострадают от какого-либо повышения производительности при вызове метода Invoke (или любого другого) из команды callvirt
несмотря на то, что она отмечена как виртуальная в IL.
Когда вызывается вызов делегата, CLR автоматически генерирует сильно оптимизированное тело для этого метода, а не компиляцию кода IL для генерации тела, которое он выполняет для "нормальных" методов. Это не имеет никакого отношения к тому, чтобы быть помеченным virtual
в IL.
Я также проверил вручную, модифицируя IL-код и повторно собрав его, чтобы виртуальный объект можно было безопасно удалить из сформированного кода IL класса класса. Сгенерированная сборка, несмотря на нарушение стандарта CIL, отлично работает.
.class private auto ansi beforefieldinit MainApp
extends [mscorlib]System.Object
{
.class auto ansi sealed nested private Echo
extends [mscorlib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname
instance void .ctor(object 'object',
native int 'method') runtime managed
{
} // end of method Echo::.ctor
.method public hidebysig instance int32 Invoke(int32 i) runtime managed
{
} // end of method Echo::Invoke
.method public hidebysig instance class [mscorlib]System.IAsyncResult
BeginInvoke(int32 i,
class [mscorlib]System.AsyncCallback callback,
object 'object') runtime managed
{
} // end of method Echo::BeginInvoke
.method public hidebysig instance int32 EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{
} // end of method Echo::EndInvoke
} // end of class Echo
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 34 (0x22)
.maxstack 3
.locals init ([0] class MainApp app,
[1] class MainApp/Echo dele)
IL_0000: nop
IL_0001: newobj instance void MainApp::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldftn instance int32 MainApp::DoEcho(int32)
IL_000e: newobj instance void MainApp/Echo::.ctor(object,
native int)
IL_0013: stloc.1
IL_0014: ldloc.1
IL_0015: ldc.i4.5
//callvirt can also be replaced by call without affecting functionality
// since delegate object is essentially not null here
IL_0016: callvirt instance int32 MainApp/Echo::Invoke(int32)
IL_001b: call void [mscorlib]System.Console::WriteLine(int32)
IL_0020: nop
IL_0021: ret
} // end of method MainApp::Main
.method private hidebysig instance int32
DoEcho(int32 i) cil managed
{
// Code size 7 (0x7)
.maxstack 1
.locals init ([0] int32 CS$1$0000)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method MainApp::DoEcho
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method MainApp::.ctor
} // end of class MainApp
Обратите внимание, что я преобразовал виртуальные методы в обычные методы экземпляра.
Поскольку этот измененный IL работает отлично, это доказывает, что стандартные обязательные виртуальные методы в запечатанном классе делегатов не нужны. Они также могут быть обычными методами.
Поэтому, по всей вероятности, эта аномалия заключается в том, чтобы подчеркнуть, что вызов этих трех методов делегирования фактически приведет к вызову какого-либо другого метода (например, полиморфизма во время выполнения, как и "обычные" виртуальные методы), или это было так, чтобы учесть некоторые будущие гипотетическое улучшение, связанное с делегатами.