Compiler generated sealed class for delegate keyword contains virtual methods

落花浮王杯 提交于 2019-11-28 21:21:20

You are being tripped up by the disassembler you used to look at the type definition. Which must translate the IL back to a recognizable language, like C#. This is not in general possible to do with full fidelity, the rules for IL are not the same as the C# language rules. This doesn't just happen for delegates, an interface implementation method is virtual as well, even though you don't declare it virtual in your C# code.

To further muddy the waters, IL actually permits a compiler to emit a non-virtual call for a virtual method if it can determine the target object from code analysis. But that will never happen for a delegate or interface call. And IL permits making a virtual call to a non-virtual method, something the C# compiler does with gusto to implement the guarantee that an instance method can never be called with a null this.

But that C# usage is a clever trick, discovered only after the CLR was designed. The original intent of virtual certainly was to annotate that the method should be called with Callvirt. Ultimately it doesn't matter because the compiler is aware of delegate and interface behavior and will always emit Callvirt. And the actual method call is implemented in CLR code which assumes a Callvirt activation.

As I noted in my question that this sealed virtual anomaly is in fact mandated by CIL standard. It remains unclear why CIL standard specifically mentions that delegate methods Invoke, BeginInvoke and EndInvoke should be virtual while at the same time mandating to seal the Delegate inherited class.

Also, after going through SSCLI code I learnt that internal optimization of JIT compiler automatically translates any callvirt call on a virtual method of a sealed class to normal call with additional null check. This means that delegates do not suffer any performance hit when its Invoke (or any of the other two) method is called through callvirt instruction despite being marked virtual in the IL.

When a delegate's invoke is called, CLR automatically emits a highly optimized body for this method as opposed to compiling IL code to generate body which it does for 'normal' methods. This has nothing to do with being marked virtual in the IL.

I have also verified by hand modifying IL code and re-assembling it that virtual can be safely removed from the generated delegate class's IL code. The generated assembly despite being in violation of the CIL standard runs perfectly fine.

.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

Note that I have converted the virtual methods to normal instance methods.

Since this changed IL runs perfectly fine it proves that standard mandated virtual methods in the sealed delegate class are not necessary. They can be normal instance methods as well.

So in all probability this anomaly is either to emphasize that calling these three delegate methods will in-fact result in calling of some other method (i.e. run-time polymorphism just like 'normal' virtual methods) or this has been so to accommodate some future hypothetical enhancement related to delegates.

This is a side-effect of the compilation process. I do not know the exact reason for this, there are more examples of this kind of behaviour. For example, a compiled static class becomes an abstract sealed class (so you can't create an instance of it, and you cannot inherit from it).

It seems not to be specific to delegates. I tried this example :

 public abstract class Base
    {
        public abstract void Test();
    }

    public sealed class Derived : Base
    {
        public override  void Test()
        {
            throw new NotImplementedException();
        }
    }

and in ILDasm I get this for the implementation of Test() :

.method public hidebysig virtual instance void 
        Test() cil managed
{
  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  newobj     instance void [mscorlib]System.NotImplementedException::.ctor()
  IL_0006:  throw
} // end of method Derived::Test

Might be that the override keyword is not a CLR keyword.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!