¿Por qué el compilador de C # emite una instrucción callvirt para una llamada al método GetType ()?

StackOverflow https://stackoverflow.com/questions/845657

Pregunta

Tengo curiosidad por saber por qué sucede esto. Lea el ejemplo de código a continuación y la IL correspondiente que se emitió en los comentarios debajo de cada sección:

using System;

class Program
{
    static void Main()
    {
        Object o = new Object();
        o.GetType();

        // L_0001: newobj instance void [mscorlib]System.Object::.ctor()
        // L_0006: stloc.0 
        // L_0007: ldloc.0 
        // L_0008: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

        new Object().GetType();

        // L_000e: newobj instance void [mscorlib]System.Object::.ctor()
        // L_0013: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
    }
}

¿Por qué el compilador emitió un callvirt para la primera sección pero un call para la segunda sección? ¿Hay alguna razón para que el compilador emita alguna vez una instrucción <=> para un método no virtual? Y si hay casos en los que el compilador emitirá un <=> para un método no virtual, ¿esto crea problemas para la seguridad de tipos?

¿Fue útil?

Solución

Solo jugando seguro.

Técnicamente, el compilador de C # no siempre usa callvirt

Para métodos estáticos & amp; métodos definidos en los tipos de valor, utiliza call. La mayoría se proporciona a través de la instrucción <=> IL.

La diferencia que cambió el voto entre los dos es el hecho de que <=> asume que el objeto " que se usa para hacer la llamada " No es nulo. <=> por otro lado, comprueba si no es nulo y lanza una NullReferenceException si es necesario.

  • Para métodos estáticos, el objeto es un objeto tipo y no puede ser nulo. Lo mismo para los tipos de valor. Por lo tanto, se utiliza <=> para ellos: mejor rendimiento.
  • Para los demás, los diseñadores de idiomas decidieron ir con <=> para que el compilador JIT verifique que el objeto que se usa para hacer la llamada no sea nulo. Incluso para los métodos de instancia no virtual ... valoraron la seguridad sobre el rendimiento.

Vea también: Jeff Richter hace un mejor trabajo en esto: en su capítulo 'Diseñando tipos' en CLR a través de C # 2nd Ed

Otros consejos

Ver esto antigua publicación de blog de Eric Gunnerson.

Aquí está el texto de la publicación:

¿Por qué C # siempre usa callvirt?

Esta pregunta surgió en un alias interno de C #, y pensé que la respuesta sería de interés general. Eso supone que la respuesta es correcta: ha pasado bastante tiempo.

El lenguaje .NET IL proporciona una instrucción call y callvirt, y callvirt se usa para llamar a funciones virtuales. Pero si observa el código que genera C #, verá que genera un & Quot; callvirt & Quot; incluso en casos donde no hay una función virtual involucrada. ¿Por qué hace eso?

Volví a las notas de diseño de lenguaje que tengo, y afirman claramente que decidimos usar callvirt el 13/12/1999. Desafortunadamente, no capturan nuestra razón para hacerlo, así que tendré que irme de mi memoria.

Recibimos un informe de alguien (probablemente uno de los grupos .NET que usa C # (pensó que todavía no se llamaba C # en ese momento)) que había escrito un código que llamaba a un método con un puntero nulo, pero no lo hicieron & # 8217; t obtuve una excepción porque el método no & # 8217; t accedió a ningún campo (es decir, & # 8220; esto & # 8221; era nulo, pero nada en el método lo usó) . Ese método luego llamó a otro método que utilizó este punto y arrojó una excepción, y se produjo un poco de rascarse la cabeza. Después de que lo descubrieron, nos enviaron una nota al respecto.

Pensamos que poder llamar a un método en una instancia nula era un poco extraño. Peter Golde hizo algunas pruebas para ver cuál era el impacto del rendimiento de usar callvirt siempre, y fue lo suficientemente pequeño como para que decidiéramos hacer el cambio.

Como un (quizás-) interesante aparte ... GetType() es inusual porque no es virtual - esto lleva a algunos cosas muy, muy extrañas .

(marcado como wiki, ya que está algo fuera del tema de la pregunta real)

El compilador no conoce el tipo real de o en la primera expresión, pero sí conoce el tipo real en la segunda expresión. Parece que solo está mirando una declaración a la vez.

Esto está bien, porque C # depende en gran medida del JIT para la optimización. Es muy probable en un caso tan simple que ambas llamadas se conviertan en llamadas de instancia en tiempo de ejecución.

No creo que alguna vez se emita callvirt para métodos no virtuales, pero incluso si lo fuera, no sería un problema porque el método nunca se anularía (por razones obvias).

Me arriesgaría a suponer que se debe a que el primero se asigna a una variable, que podría contener una instancia descartada de otro tipo que podría haber anulado GetType (aunque podemos ver que no); el segundo nunca podría ser otra cosa que Object.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top