'is' rispetto a try cast con controllo null


107

Ho notato che Resharper suggerisce di attivare questo:

if (myObj.myProp is MyType)
{
   ...
}

in questo:

var myObjRef = myObj.myProp as MyType;
if (myObjRef != null)
{
   ...
}

Perché suggerirebbe questo cambiamento? Sono abituato a Resharper suggerire modifiche all'ottimizzazione e modifiche alla riduzione del codice, ma sembra che voglia prendere la mia singola affermazione e trasformarla in una doppia riga.

Secondo MSDN :

Una è espressione risulta vera se entrambe le seguenti condizioni:

l'espressione non è nulla. è possibile eseguire il cast dell'espressione al tipo . Ovvero, un'espressione cast del modulo (type)(expression)verrà completata senza generare un'eccezione.

Lo sto interpretando erroneamente o non isfaccio gli stessi identici controlli, solo in una singola riga senza la necessità di creare esplicitamente un'altra variabile locale per il controllo nullo?


1
stai usando myObjRef più avanti nel codice? se lo sei, non avresti bisogno del MyPropgetter dopo questo cambiamento.
Predefinito

Risposte:


147

Perché c'è solo un cast. Confronta questo:

if (myObj.myProp is MyType) // cast #1
{
    var myObjRef = (MyType)myObj.myProp; // needs to be cast a second time
                                         // before using it as a MyType
    ...
}

a questa:

var myObjRef = myObj.myProp as MyType; // only one cast
if (myObjRef != null)
{
    // myObjRef is already MyType and doesn't need to be cast again
    ...
}

C # 7.0 supporta una sintassi più compatta utilizzando la corrispondenza dei modelli :

if (myObj.myProp is MyType myObjRef)
{
    ...
}

3
Esattamente. usare 'is' sta fondamentalmente facendo qualcosa come return ((myProp as MyType) == null)
Bambu

2
Per quanto riguarda i cambiamenti, però, questo è piuttosto minuto. Il controllo nullo sarà abbastanza paragonabile al secondo tipo di controllo. aspuò essere un paio di nanosecondi più veloce, ma la considero una microottimizzazione prematura.
Servizio

4
Si noti inoltre che la versione originale non è thread-safe. Il valore di myObjo myProppotrebbe essere modificato (da un altro thread) tra il ise il cast, causando un comportamento indesiderato.
Jeff E

1
Potrei anche aggiungere che l'uso di as+ != nulleseguirà anche l' !=operatore sovrascritto di MyTypese definito (anche se myObjRefè nullo). Sebbene nella maggior parte dei casi questo non sia un problema (specialmente se lo si implementa correttamente), in alcuni casi estremi (codice errato, prestazioni) potrebbe non essere desiderato. (Dovrebbe essere piuttosto estremo però)
Chris Sinclair

1
@ Chris: Giusto, la traduzione corretta del codice userebbe object.ReferenceEquals(null, myObjRef).
Ben Voigt

10

L'opzione migliore è usare la corrispondenza del modello in questo modo:

if (value is MyType casted){
    //Code with casted as MyType
    //value is still the same
}
//Note: casted can be used outside (after) the 'if' scope, too

In che modo esattamente questo è migliore del secondo frammento della domanda?
Victor Yarema il

Il secondo frammento della domanda si riferisce all'uso di base di is (senza la dichiarazione della variabile) e in tal caso controllerai il tipo due volte (una nell'istruzione is e un'altra prima del cast)
Francesco Cattoni

6

Non ci sono ancora informazioni su ciò che accade effettivamente sotto la cintura. Dai un'occhiata a questo esempio:

object o = "test";
if (o is string)
{
    var x = (string) o;
}

Questo si traduce nel seguente IL:

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  ldnull      
IL_000E:  cgt.un      
IL_0010:  stloc.1     
IL_0011:  ldloc.1     
IL_0012:  brfalse.s   IL_001D
IL_0014:  nop         
IL_0015:  ldloc.0     // o
IL_0016:  castclass   System.String
IL_001B:  stloc.2     // x
IL_001C:  nop         
IL_001D:  ret   

Ciò che conta qui sono le chiamate isinste castclass, entrambe relativamente costose. Se lo confronti con l'alternativa puoi vedere che fa solo un isinstcontrollo:

object o = "test";
var oAsString = o as string;
if (oAsString != null)
{

}

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  stloc.1     // oAsString
IL_000E:  ldloc.1     // oAsString
IL_000F:  ldnull      
IL_0010:  cgt.un      
IL_0012:  stloc.2     
IL_0013:  ldloc.2     
IL_0014:  brfalse.s   IL_0018
IL_0016:  nop         
IL_0017:  nop         
IL_0018:  ret  

Vale anche la pena ricordare che un tipo di valore utilizzerà unbox.anyinvece di castclass:

object o = 5;
if (o is int)
{
    var x = (int)o;
}

IL_0000:  nop         
IL_0001:  ldc.i4.5    
IL_0002:  box         System.Int32
IL_0007:  stloc.0     // o
IL_0008:  ldloc.0     // o
IL_0009:  isinst      System.Int32
IL_000E:  ldnull      
IL_000F:  cgt.un      
IL_0011:  stloc.1     
IL_0012:  ldloc.1     
IL_0013:  brfalse.s   IL_001E
IL_0015:  nop         
IL_0016:  ldloc.0     // o
IL_0017:  unbox.any   System.Int32
IL_001C:  stloc.2     // x
IL_001D:  nop         
IL_001E:  ret   

Nota tuttavia che questo non si traduce necessariamente in un risultato più veloce come possiamo vedere qui . Sembra che ci sono stati miglioramenti poiché tale questione è stato chiesto se: calchi sembrano essere eseguito velocemente come quelli di una volta, ma ase linqora sono circa 3 volte più veloce.


4

Avviso di affilatura:

"Type check and direct cast can be replaced with try cast and check for null"

Entrambi funzioneranno, dipende da come il tuo codice ti si addice di più. Nel mio caso ignoro semplicemente quell'avvertimento:

//1st way is n+1 times of casting
if (x is A) ((A)x).Run();
else if (x is B) ((B)x).Run();
else if (x is C) ((C)x).Run();
else if (x is D) ((D)x).Run();
//...
else if (x is N) ((N)x).Run();    
//...
else if (x is Z) ((Z)x).Run();

//2nd way is z times of casting
var a = x as Type A;
var b = x as Type B;
var c = x as Type C;
//..
var n = x as Type N;
//..
var z = x as Type Z;
if (a != null) a.Run();
elseif (b != null) b.Run();
elseif (c != null) c.Run();
...
elseif (n != null) n.Run();
...
elseif (x != null) x.Run();

Nel mio codice il secondo modo è più lungo e peggiore delle prestazioni.


1
Nel tuo esempio del mondo reale, c'è semplicemente un problema di progettazione. Se controlli i tipi, usa semplicemente un'interfaccia come IRunable. Se non hai il controllo, forse potresti usarlo dynamic?
M. Mimpen

3

A me questo sembra dipendere da quali sono le probabilità che sia di quel tipo o meno. Sarebbe certamente più efficiente eseguire il cast in anticipo se l'oggetto è di quel tipo la maggior parte del tempo. Se è solo occasionalmente di quel tipo, potrebbe essere più ottimale controllare prima con is.

Il costo della creazione di una variabile locale è molto trascurabile rispetto al costo del controllo del tipo.

La leggibilità e la portata sono i fattori più importanti per me in genere. Non sarei d'accordo con ReSharper e utilizzerei l'operatore "is" solo per questo motivo; ottimizzare in seguito se si tratta di un vero collo di bottiglia.

(Presumo che tu stia usando solo myObj.myProp is MyTypeuna volta in questa funzione)


0

Dovrebbe suggerire anche una seconda modifica:

(MyType)myObj.myProp

in

myObjRef

Ciò consente di risparmiare un accesso alla proprietà e un cast, rispetto al codice originale. Ma è possibile solo dopo il passaggio isa as.


@ Default: No, non lo è. Ciò non significa che non sia nel codice.
Ben Voigt

1
scusa .. incompreso. tuttavia, (MyType)genererà un'eccezione se il cast fallisce. asrestituisce solo null.
Predefinito

@Default: il cast non fallirà, perché il tipo è già stato controllato con is(quel codice è nella domanda).
Ben Voigt

1
tuttavia, re # vuole sostituire quel codice, il che significa che non sarebbe lì dopo la modifica suggerita.
Predefinito

Io penso che sto seguendo il vostro pensiero qui (solo mi ha portato un po 'di tempo). Vuoi dire che la prima riga è da qualche parte nel codice e quella riga sarebbe semplificata dopo il suggerimento Re # alla seconda riga?
Predefinito

0

Direi che questo è per creare una versione fortemente tipizzata di myObj.myProp, che è myObjRef. Questo dovrebbe quindi essere utilizzato quando si fa riferimento a questo valore nel blocco, invece di dover eseguire un cast.

Ad esempio, questo:

myObjRef.SomeProperty

è meglio di questo:

((MyType)myObj.myProp).SomeProperty
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.