sellada property method herencia español clase and c# .net delegates cil il

property - virtual and override c#



El compilador generó una clase sellada para la palabra clave de delegado que contiene métodos virtuales (4)

Cuando se utiliza la palabra clave delegate en C #, el compilador C # genera automáticamente una clase derivada de la clase System.MulticastDelegate .

Esta clase generada por el compilador también contiene 3 métodos: Invoke, BeginInvoke and EndInvoke .

Todos estos tres métodos están marcados como public virtual extern pero curiosamente la clase en sí está marcada como sealed .

Los métodos virtuales definidos en una clase sellada no solo son contraintuitivos sino que en realidad son ilegales en C #.

Entonces mi pregunta es, ¿hay alguna razón específica para esto o es solo una de esas cosas inofensivas hechas teniendo en cuenta alguna mejora futura hipotética?

Editar 1:

¿Puede el motivo ser forzar el uso del código de operación IL de "callVirt" en lugar de "llamar" para que CLR siempre verifique el objeto delegado antes de intentar ejecutar cualquiera de los tres métodos? Aunque no entiendo por qué un delegate debería ser un caso especial a este respecto.

Además, ¿no es un golpe de rendimiento forzar el uso de callvirt (aunque puede ser minúsculo)?

Editar 2:

Se agregó una etiqueta CIL, ya que resulta que la manera C # de definir delegados es, de hecho, un mandato del estándar CIL. El estándar indica que (el siguiente texto no es completo)

Los delegados deben tener un tipo base de System.Delegate. Los delegados serán declarados sellados , y los únicos miembros que tendrá un delegado serán los primeros dos o los cuatro métodos como se especifica aquí. Estos métodos se declararán en tiempo de ejecución y administrados. No deben tener un cuerpo, ya que ese cuerpo debe ser creado automáticamente por el VES. Otros métodos disponibles en los delegados se heredan de la clase System.Delegate en la Biblioteca de clases base. Los métodos de delegado son:

  1. El constructor de la instancia
  2. El método Invoke será virtual
  3. El método BeginInvoke, si está presente, será virtual
  4. El método EndInvoke será virtual

Así que esto definitivamente no es un efecto secundario del proceso del compilador o es similar a otros resultados interesantes del compilador.

Si el estándar enfatiza algo, debe ser por alguna buena razón y justificación.

Entonces, la pregunta ahora es ¿por qué el estándar CIL para delegados enfatiza en sellado y virtual al mismo tiempo?

¿La captura está aquí?

No deben tener un cuerpo, ya que ese cuerpo debe ser creado automáticamente por el VES.

¿Están marcados como virtuales para que el cuerpo generado VES / CLR pueda ejecutarse al invocar estos métodos?


Como señalé en mi pregunta, esta anomalía virtual sellada es, de hecho, un mandato del estándar CIL. Todavía no está claro por qué el estándar CIL menciona específicamente que los métodos de delegado Invoke , BeginInvoke y EndInvoke deben ser virtuales, mientras que al mismo tiempo obligan a sellar la clase heredada del Delegate .

Además, después de pasar por el código SSCLI, descubrí que la optimización interna del compilador JIT traduce automáticamente cualquier llamada callvirt en un método virtual de una clase sellada a una llamada normal con verificación nula adicional. Esto significa que los delegados no sufren ningún golpe de rendimiento cuando se llama a su método Invocar (o a cualquiera de los otros dos) a través de la instrucción callvirt pesar de estar marcado como virtual en el IL.

Cuando se invoca una invocación de un delegado, CLR emite automáticamente un cuerpo altamente optimizado para este método en lugar de compilar el código IL para generar el cuerpo que lo hace para los métodos "normales". Esto no tiene nada que ver con ser marcado virtual en IL.

También he verificado manualmente la modificación del código IL y su reensamblaje para que se pueda eliminar de manera segura del código IL de la clase delegada generada. El ensamblaje generado a pesar de estar en violación del estándar CIL funciona perfectamente bien.

.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

Tenga en cuenta que he convertido los métodos virtuales a métodos de instancia normales.

Como esto cambió IL funciona perfectamente bien, prueba que los métodos virtuales obligatorios estándar en la clase de delegado sellado no son necesarios. También pueden ser métodos de instancia normales.

Entonces, con toda probabilidad, esta anomalía es enfatizar que llamar a estos tres métodos delegados resultará en la invocación de algún otro método (es decir, polimorfismo en tiempo de ejecución al igual que los métodos virtuales "normales") o esto ha sido para acomodar un futuro mejora hipotética relacionada con los delegados.


Estás siendo derribado por el desensamblador que usaste para mirar la definición de tipo. Lo cual debe traducir el IL a un lenguaje reconocible, como C #. En general, esto no se puede hacer con total fidelidad, las reglas para IL no son las mismas que las reglas de lenguaje C #. Esto no sucede solo para los delegados, un método de implementación de interfaz también es virtual, aunque no lo declare virtual en su código C #.

Para ensuciar aún más las aguas, IL realmente permite que un compilador emita una llamada no virtual para un método virtual si puede determinar el objeto objetivo a partir del análisis del código. Pero eso nunca sucederá para un delegado o una llamada de interfaz. E IL permite hacer una llamada virtual a un método no virtual, algo que el compilador de C # hace con gusto para implementar la garantía de que nunca se puede invocar un método de instancia con un valor nulo.

Pero ese uso de C # es un truco ingenioso, descubierto solo después de que se diseñó el CLR. La intención original de Virtual ciertamente era anotar que el método debería ser llamado con Callvirt. En última instancia, no importa porque el compilador conoce el comportamiento del delegado y la interfaz y siempre emitirá Callvirt. Y la llamada al método real se implementa en el código CLR que asume una activación Callvirt.


Este es un efecto secundario del proceso de compilación. No sé la razón exacta de esto, hay más ejemplos de este tipo de comportamiento. Por ejemplo, una clase estática compilada se convierte en una clase abstracta cerrada (por lo que no puede crear una instancia de la misma y no puede heredarla).


Parece que no es específico para los delegados. Intenté este ejemplo:

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

y en ILDasm obtengo esto para la implementación de 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

Puede ser que la palabra clave de anulación no sea CLR.