Perché il compilatore C # sta emettendo un'istruzione callvirt per una chiamata al metodo GetType ()?

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

Domanda

Sono curioso di sapere perché questo sta accadendo. Leggi l'esempio di codice seguente e l'IL corrispondente che è stato emesso nei commenti sotto ogni sezione:

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()
    }
}

Perché il compilatore ha emesso un callvirt per la prima sezione ma un call per la seconda sezione? C'è qualche motivo per cui il compilatore dovrebbe mai emettere un'istruzione <=> per un metodo non virtuale? E se ci sono casi in cui il compilatore emetterà un <=> per un metodo non virtuale, questo crea problemi per la sicurezza dei tipi?

È stato utile?

Soluzione

Basta giocare in sicurezza.

Tecnicamente il compilatore C # non sempre usa callvirt

Per metodi statici & amp; metodi definiti su tipi di valore, utilizza call. La maggior parte viene fornita tramite l'istruzione <=> IL.

La differenza che ha oscillato il voto tra i due è il fatto che <=> assume il " oggetto utilizzato per effettuare la chiamata " non è nullo. <=> d'altra parte controlla se non è null e genera un'eccezione NullReferenceException se necessario.

  • Per i metodi statici, l'oggetto è un oggetto tipo e non può essere nullo. Idem per i tipi di valore. Quindi <=> viene usato per loro - prestazioni migliori.
  • Per gli altri, i progettisti del linguaggio hanno deciso di scegliere <=>, quindi il compilatore JIT verifica che l'oggetto utilizzato per effettuare la chiamata non sia nullo. Anche per i metodi di istanza non virtuali .. hanno valutato la sicurezza rispetto alle prestazioni.

Vedi anche: Jeff Richter fa un lavoro migliore in questo - nel suo capitolo "Tipi di progettazione" in CLR tramite C # 2nd Ed

Altri suggerimenti

Vedi questo vecchio post sul blog di Eric Gunnerson.

Ecco il testo del post:

Perché C # usa sempre callvirt?

Questa domanda è stata posta su un alias C # interno e ho pensato che la risposta fosse di interesse generale. Ciò presuppone che la risposta sia corretta - è passato parecchio tempo.

Il linguaggio .NET IL fornisce sia un'istruzione call che callvirt, con callvirt usato per chiamare funzioni virtuali. Ma se guardi attraverso il codice generato da C #, vedrai che genera un & Quot; callvirt & Quot; anche nei casi in cui non è coinvolta alcuna funzione virtuale. Perché lo fa?

Ho ripercorso le note sulla progettazione della lingua che ho e affermano chiaramente che abbiamo deciso di utilizzare callvirt il 13/12/1999. Sfortunatamente, non catturano la nostra logica per farlo, quindi dovrò andare dalla mia memoria.

Avevamo ricevuto un rapporto da qualcuno (probabilmente uno dei gruppi .NET che utilizzava C # (pensavo che non fosse ancora chiamato C # in quel momento)) che aveva scritto codice che chiamava un metodo su un puntatore null, ma non lo fecero & # 8217; t ottiene un'eccezione perché il metodo non ha & # 8217; t ha accesso a tutti i campi (es. & # 8220; questo & # 8221; era nullo, ma nel metodo non è stato utilizzato nulla) . Quel metodo ha quindi chiamato un altro metodo che ha utilizzato questo punto e ha generato un'eccezione, e ne è seguito un po 'di graffi. Dopo averlo capito, ci hanno inviato un messaggio al riguardo.

Pensavamo che poter chiamare un metodo su un'istanza nulla fosse un po 'strano. Peter Golde ha fatto alcuni test per vedere quale fosse l'impatto perfetto di usare sempre callvirt ed era abbastanza piccolo che abbiamo deciso di apportare la modifica.

Come un (forse-) interessante a parte ... GetType() è insolito in quanto non è virtual - questo porta ad alcuni cose molto, molto strane .

(contrassegnato come wiki in quanto leggermente fuori tema rispetto alla domanda reale)

Il compilatore non conosce il tipo reale di o nella prima espressione, ma conosce il tipo reale nella seconda espressione. Sembra che guardi solo un'istruzione alla volta.

Questo va bene, perché C # dipende fortemente dalla JIT per l'ottimizzazione. È molto probabile in un caso così semplice che entrambe le chiamate diventeranno chiamate di istanza in fase di esecuzione.

Non credo che callvirt sia mai stato emesso per metodi non virtuali, ma anche se lo fosse, non sarebbe un problema perché il metodo non verrebbe mai ignorato (per ovvi motivi).

Immagino che sia perché il primo assegna a una variabile, che potenzialmente potrebbe contenere un'istanza di downcast di un altro tipo che potrebbe avere ignorato GetType (anche se possiamo vedere che non lo fa); il secondo non potrebbe mai essere altro che Object.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top