'is' versus try cast avec vérification nulle

107

J'ai remarqué que Resharper suggère que je tourne ceci:

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

dans ceci:

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

Pourquoi suggérerait-il ce changement? J'ai l'habitude de Resharper suggérant des changements d'optimisation et des changements de réduction de code, mais j'ai l'impression qu'il veut prendre ma seule déclaration et la transformer en deux lignes.

Selon MSDN :

Une expression is prend la valeur true si les deux conditions suivantes sont remplies:

expression n'est pas nulle. l'expression peut être convertie en type . Autrement dit, une expression transtypée du formulaire (type)(expression)se terminera sans lever d'exception.

Suis-je mal interprété cela, ou ne isfais-je pas exactement les mêmes vérifications, juste en une seule ligne sans avoir besoin de créer explicitement une autre variable locale pour la vérification nulle?

HotN
la source
1
utilisez-vous myObjRef plus tard dans le code? si vous l'êtes, vous n'aurez pas besoin du MyPropgetter après ce changement.
Par défaut

Réponses:

147

Parce qu'il n'y a qu'un seul casting. Comparez ceci:

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

pour ça:

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 prend en charge une syntaxe plus compacte utilisant la correspondance de modèles :

if (myObj.myProp is MyType myObjRef)
{
    ...
}
Jeff E
la source
3
exactement. l'utilisation de 'is' fait essentiellement quelque chose comme return ((myProp as MyType) == null)
Bambu
2
En ce qui concerne les changements, c'est assez minime. La vérification nulle sera assez comparable à la deuxième vérification de type. aspeut être quelques nanosecondes plus rapide, mais je considère cela comme une microoptimisation prématurée.
Servy
4
Notez également que la version d'origine n'est pas thread-safe. La valeur de myObjou myProppourrait être modifiée (par un autre thread) entre le iset le cast, provoquant un comportement indésirable.
Jeff E
1
Je pourrais également ajouter que l'utilisation de as+ != nullexécutera également l' !=opérateur surchargé de MyTypeif défini (même s'il myObjRefest nul). Bien que dans la plupart des cas ce ne soit pas un problème (en particulier si vous l'implémentez correctement), dans certains cas extrêmes (mauvais code, performances), cela peut ne pas être souhaité. (devrait être assez extrême cependant)
Chris Sinclair
1
@Chris: C'est vrai, la traduction correcte du code utiliserait object.ReferenceEquals(null, myObjRef).
Ben Voigt
10

La meilleure option est d'utiliser la correspondance de motifs comme celle-ci:

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
la source
En quoi exactement celui-ci est-il meilleur que le deuxième fragment de la question?
Victor Yarema le
Le deuxième fragment de la question fait référence à l'utilisation de base de is (sans la déclaration de variable) et dans ce cas, vous vérifierez le type deux fois (un dans l'instruction is et un autre avant le casting)
Francesco Cattoni
6

Il n'y a pas encore d'informations sur ce qui se passe réellement sous la ceinture. Jetez un œil à cet exemple:

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

Cela se traduit par l'IL suivante:

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   

Ce qui compte ici, ce sont les appels isinstet castclass- tous deux relativement chers. Si vous comparez cela à l'alternative, vous pouvez voir que cela ne fait qu'une isinstvérification:

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  

Il convient également de mentionner qu'un type valeur utilisera unbox.anyplutôt que 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   

Notez cependant que cela ne se traduit pas nécessairement par un résultat plus rapide comme nous pouvons le voir ici . Il semble y avoir eu des améliorations depuis cette question a été posée si: moulages semblent être effectuées aussi vite qu'ils l' habitude d'être , mais aset linqsont maintenant environ 3 fois plus rapide.

Jeroen Vannevel
la source
4

Avertissement de réaffûtage:

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

Les deux fonctionneront, cela dépend de la façon dont votre code vous convient le plus. Dans mon cas, j'ignore simplement cet avertissement:

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

Dans mon code, la deuxième manière est plus longue et moins performante.

À M
la source
1
Dans votre exemple réel, il y a simplement un problème de conception. Si vous contrôlez les types, utilisez simplement une interface telle que IRunable. Si vous n'avez pas le contrôle, vous pourriez peut-être utiliser dynamic?
M. Mimpen
3

Pour moi, cela semble dépendre des chances que ce soit de ce type ou non. Il serait certainement plus efficace de faire le casting à l'avant si l'objet est de ce type la plupart du temps. Si ce n'est qu'occasionnellement de ce type, il peut être plus optimal de vérifier d'abord avec is.

Le coût de création d'une variable locale est très négligeable par rapport au coût du contrôle de type.

La lisibilité et la portée sont généralement les facteurs les plus importants pour moi. Je ne serais pas d'accord avec ReSharper et utiliserais l'opérateur "is" pour cette seule raison; optimisez plus tard s'il s'agit d'un véritable goulot d'étranglement.

(Je suppose que vous n'utilisez myObj.myProp is MyTypequ'une seule fois dans cette fonction)

Derrick
la source
0

Cela devrait également suggérer un deuxième changement:

(MyType)myObj.myProp

dans

myObjRef

Cela enregistre un accès à la propriété et une distribution, par rapport au code d'origine. Mais ce n'est possible qu'après le passage isà as.

Ben Voigt
la source
@Default: Non, ce n'est pas le cas. Cela ne veut pas dire que ce n'est pas dans le code.
Ben Voigt
1
désolé .. incompris. cependant, (MyType)lèvera une exception si la conversion échoue. asne retourne que null.
Par défaut
@Default: Le cast n'échouera pas, car le type a déjà été vérifié avec is(ce code est dans la question).
Ben Voigt
1
cependant, re # veut remplacer ce code - ce qui signifie qu'il ne serait plus là après le changement suggéré.
Par défaut
Je pense que je suis votre pensée ici (cela m'a juste pris du temps). Vous voulez dire que la première ligne est quelque part dans le code et que cette ligne serait simplifiée après la suggestion Re # à la deuxième ligne?
Par défaut
0

Je dirais que c'est pour créer une version fortement typée de myObj.myProp, qui est myObjRef. Cela doit ensuite être utilisé lorsque vous faites référence à cette valeur dans le bloc, plutôt que de devoir effectuer un cast.

Par exemple, ceci:

myObjRef.SomeProperty

c'est mieux que ça:

((MyType)myObj.myProp).SomeProperty
Jerad Rose
la source