'is' versus try cast mit null check

105

Mir ist aufgefallen, dass Resharper vorschlägt, dies zu ändern:

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

das mögen:

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

Warum sollte es diese Änderung vorschlagen? Ich bin es gewohnt, mit Resharper Optimierungs- und Code-Reduktionsänderungen vorzuschlagen, aber es scheint, als würde ich meine einzelne Anweisung in einen Zweiliner umwandeln wollen.

Laut MSDN :

Ein is- Ausdruck wird als wahr ausgewertet, wenn beide der folgenden Bedingungen erfüllt sind:

Ausdruck ist nicht null. Ausdruck kann in Typ umgewandelt werden . Das heißt, ein Besetzungsausdruck des Formulars (type)(expression)wird ohne Ausnahme abgeschlossen.

Verstehe ich das falsch oder mache nicht isgenau die gleichen Prüfungen nur in einer einzigen Zeile, ohne dass explizit eine andere lokale Variable für die Nullprüfung erstellt werden muss?

Heiss und
quelle
1
Verwenden Sie myObjRef später im Code? Wenn Sie es sind, würden Sie den MyPropGetter nach dieser Änderung nicht brauchen .
Standard

Antworten:

144

Weil es nur eine Besetzung gibt. Vergleichen Sie dies:

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
    ...
}

dazu:

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 unterstützt eine kompaktere Syntax mit Pattern Matching :

if (myObj.myProp is MyType myObjRef)
{
    ...
}
Jeff E.
quelle
3
genau. Die Verwendung von 'is' bedeutet im Grunde etwas wie return ((myProp als MyType) == null)
Bambu
2
Was die Änderungen angeht, ist dies ziemlich winzig. Die Nullprüfung wird ziemlich vergleichbar mit der zweiten Typprüfung sein. asmag ein paar Nanosekunden schneller sein, aber ich halte dies für eine vorzeitige Mikrooptimierung.
Servy
4
Beachten Sie auch, dass die Originalversion nicht threadsicher ist. Der Wert von myObjoder myPropkönnte (durch einen anderen Thread) zwischen dem isund dem Cast geändert werden , was zu unerwünschtem Verhalten führt.
Jeff E
1
Ich könnte auch hinzufügen, dass die Verwendung von as+ != nullauch den überschriebenen !=Operator von MyTypeif ausführt (auch wenn er myObjRefnull ist). Während dies in den meisten Fällen kein Problem darstellt (insbesondere wenn Sie es ordnungsgemäß implementieren), ist es in einigen extremen Fällen (schlechter Code, Leistung) möglicherweise nicht erwünscht. (müsste allerdings ziemlich extrem sein )
Chris Sinclair
1
@ Chris: Richtig, die korrekte Übersetzung des Codes würde verwenden object.ReferenceEquals(null, myObjRef).
Ben Voigt
10

Die beste Option ist die Verwendung eines solchen Mustervergleichs:

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
Francesco Cattoni
quelle
Wie genau ist dieses besser als das zweite Fragment aus der Frage?
Victor Yarema
Das zweite Fragment der Frage bezieht sich auf die grundlegende Verwendung von is (ohne die Variablendeklaration). In diesem Fall überprüfen Sie den Typ zweimal (eine in der is-Anweisung und eine andere vor der Besetzung)
Francesco Cattoni
6

Es gibt noch keine Informationen darüber, was tatsächlich unter der Gürtellinie passiert. Schauen Sie sich dieses Beispiel an:

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

Dies führt zu folgender 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   

Was hier zählt, sind die isinstund castclassAnrufe - beide relativ teuer. Wenn Sie das mit der Alternative vergleichen, sehen Sie, dass nur eine isinstÜberprüfung durchgeführt wird:

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  

Erwähnenswert ist auch, dass ein Werttyp unbox.anyeher Folgendes verwendet als 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   

Beachten Sie jedoch, dass dies nicht unbedingt zu einem schnelleren Ergebnis führt, wie wir hier sehen können . Es scheint Verbesserungen gewesen zu sein , da diese Frage gestellt wurde , obwohl: Abgüsse scheinen so schnell durchgeführt werden , wie sie verwendet werden , aber asund linqsind jetzt etwa 3 - mal schneller.

Jeroen Vannevel
quelle
4

Warnung zum Nachschärfen:

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

Beides wird funktionieren, es hängt davon ab, wie Ihr Code besser zu Ihnen passt. In meinem Fall ignoriere ich diese Warnung einfach:

//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();

In meinem Code ist der 2. Weg länger und die Leistung schlechter.

Tom
quelle
1
In Ihrem realen Beispiel gibt es einfach ein Designproblem. Wenn Sie die Typen steuern, verwenden Sie einfach eine Schnittstelle wie z IRunable. Wenn Sie nicht die Kontrolle haben, könnten Sie vielleicht verwenden dynamic?
M. Mimpen
3

Für mich scheint dies davon abhängig zu sein, wie hoch die Wahrscheinlichkeit ist, dass es von diesem Typ sein wird oder nicht. Es wäre sicherlich effizienter, die Besetzung im Voraus durchzuführen, wenn das Objekt die meiste Zeit von diesem Typ ist. Wenn es nur gelegentlich von diesem Typ ist, ist es möglicherweise optimaler, zuerst mit is zu prüfen.

Die Kosten für die Erstellung einer lokalen Variablen sind im Vergleich zu den Kosten für die Typprüfung sehr vernachlässigbar.

Lesbarkeit und Umfang sind für mich in der Regel die wichtigsten Faktoren. Ich würde ReSharper nicht zustimmen und den Operator "is" allein aus diesem Grund verwenden. Optimieren Sie später, wenn dies ein echter Engpass ist.

(Ich gehe davon aus, dass Sie myObj.myProp is MyTypein dieser Funktion nur einmal verwenden)

Bohrturm
quelle
0

Es sollte auch eine zweite Änderung vorschlagen:

(MyType)myObj.myProp

in

myObjRef

Dies spart einen Eigenschaftszugriff und eine Besetzung im Vergleich zum ursprünglichen Code. Dies ist jedoch erst nach dem Wechsel iszu möglich as.

Ben Voigt
quelle
@Default: Nein, ist es nicht. Das heißt nicht, dass es nicht im Code enthalten ist.
Ben Voigt
1
Entschuldigung .. missverstanden. jedoch (MyType)wird werfen Ausnahme , wenn die Umwandlung fehlschlägt. askehrt nur zurück null.
Standard
@Default: Die Umwandlung schlägt nicht fehl, da der Typ bereits überprüft wurde is(dieser Code ist in Frage).
Ben Voigt
1
re # möchte diesen Code jedoch ersetzen - was bedeutet, dass er nach der vorgeschlagenen Änderung nicht mehr vorhanden ist.
Standard
Ich glaube, ich folge Ihrem Gedanken hier (ich habe nur einige Zeit gebraucht). Sie meinen , dass die erste Zeile ist irgendwo im Code und die Leitung würde in die zweite Zeile nach dem Re # Vorschlag vereinfacht werden?
Standard
0

Ich würde sagen, dies ist eine stark typisierte Version von myObj.myProp, nämlich myObjRef. Dies sollte dann verwendet werden, wenn Sie auf diesen Wert im Block verweisen, anstatt einen Cast durchführen zu müssen.

Zum Beispiel:

myObjRef.SomeProperty

ist besser als das:

((MyType)myObj.myProp).SomeProperty
Jerad Rose
quelle