2015-12-30 66 views
11

Dati i due metodi:anziché callvirt in caso di nuova C# 6 "?" nullo controllare

static void M1(Person p) 
    { 
     if (p != null) 
     { 
      var p1 = p.Name; 
     } 
    } 

    static void M2(Person p) 
    { 
     var p1 = p?.Name; 
    } 

Perché l'uso M1 IL codice callvirt:

IL_0007: brfalse.s IL_0012 
IL_0009: nop 
IL_000a: ldarg.0 
IL_000b: callvirt instance string ConsoleApplication4.Person::get_Name() 

e M2 IL utilizzano call:

brtrue.s IL_0007 
IL_0004: ldnull 
IL_0005: br.s  IL_000d 
IL_0007: ldarg.0 
IL_0008: call  instance string ConsoleApplication4.Person::get_Name() 

posso solo immaginare che perché in M2 sappiamo che p non è nullo e corrisponde a

new MyClass().MyMethod(); 

È vero?

Se lo è, cosa succede se p sarà null in altro thread?

+0

Sarebbe bello mostrare come viene chiamato un membro virtuale effettivo quando sottoposto a override. Specificamente in presenza di '? .'. Qual è il comportamento quando un membro sottoposto a override viene 'sealed' e invocato in modo specifico? Se è ancora 'callvirt', potrebbe essere un'opportunità di ottimizzazione in Roslyn :) – leppie

+0

@leppie C# genera' callvirt' per questo. Ma non so cosa succederà al runtime. –

risposta

5

Penso che sia chiaramente ora,

Questo è un modo semplice e thread-safe per verificare la presenza di null prima di attivare un evento. Il motivo per cui è sicuro per i thread è che la funzione valuta il lato sinistro solo una volta e la mantiene in una variabile temporanea. MSDN

Quindi è possibile utilizzare l'istruzione call qui.

ho scritto una blog post sulle differenze tra call e callvirt e perché C# generano callvirt

Grazie Dan Lyons per il link MSDN.

7

Il callvirt in M1 è standard C# code generation. Fornisce la garanzia della lingua che un metodo di istanza non può mai essere chiamato con riferimento null. In altre parole, garantisce che p != null e genera NullReferenceException se è nullo. Il tuo test esplicito non lo cambia.

Questa garanzia è molto carina, il debugging NRE diventa piuttosto peloso se è this che è nullo. Invece, molto più facile da diagnosticare l'incidente sul sito di chiamata, il debugger può mostrare rapidamente che è lo p che è il troublemaker.

Ma ovviamente callvirt non è gratuito, anche se il costo è molto basso, un'ulteriore istruzione del processore in fase di esecuzione. Quindi se è può essere sostituito da call allora il codice sarà più veloce di mezzo nanosecondo, dare o avere. In effetti, può farlo con l'operatore elvis poiché garantisce già che il riferimento non sia nullo, quindi il compilatore C# 6 ne ha approfittato e genera chiamate anziché callvirt.

+0

grazie per i dettagli. Ho scritto su di esso un post nel link sopra, ma non sapevo circa il mezzo nanosecondo :) –

1

Inizia con il fatto che viene utilizzato callvirt invece di call a causa della regola C# che gli oggetti Null potrebbero non avere metodi chiamati su di essi, anche quando .NET lo consente.

Ora, in entrambi i metodi, possiamo dimostrare che staticamente p non è nullo, e come tale utilizzando call invece di callvirt non sta per rompere questa regola # C, e come tale è un'ottimizzazione ragionevole.

Mentre if (a != null) a.b ecc è un linguaggio comune, prende analisi rendersi conto che a non può essere nullo al punto che b viene utilizzato. L'aggiunta di tale analisi al compilatore richiederebbe il lavoro di specing, implementazione, testing e testing continuo contro i bug di regressione introdotti da altre modifiche.

a?.b è oltre un idioma, in quanto sta utilizzando un operatore ?. che C# deve "conoscere". Quindi C# deve avere il codice per trasformarlo in un controllo nullo seguito da un accesso membro. Quindi il compilatore deve sapere che nel punto in cui avviene l'accesso del membro, a non è nullo. Come tale, la logica per "sapere" che l'uso di call è sicuro è già stato fatto. Non è necessario eseguire ulteriori analisi per capire che è possibile utilizzare call.

Quindi il primo caso richiederebbe un sacco di lavoro aggiuntivo per utilizzare call e potenzialmente introdurre bug, mentre il secondo caso deve farlo funzionare comunque, quindi potrebbe anche.

Problemi correlati