'is' versus try cast con check nulo

106

Noté que Resharper sugiere que cambie esto:

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

dentro de esto:

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

¿Por qué sugeriría este cambio? Estoy acostumbrado a que Resharper sugiera cambios de optimización y cambios de reducción de código, pero parece que quiere tomar mi declaración única y convertirla en dos líneas.

Según MSDN :

Una expresión is se evalúa como verdadera si se cumplen las dos condiciones siguientes:

la expresión no es nula. la expresión se puede convertir al tipo . Es decir, una expresión de conversión del formulario (type)(expression)se completará sin generar una excepción.

¿Estoy malinterpretando eso, o no ishago exactamente las mismas verificaciones, solo en una sola línea sin la necesidad de crear explícitamente otra variable local para la verificación nula?

HotN
fuente
1
¿Está usando myObjRef más adelante en el código? si es así, no necesitaría el MyPropgetter después de este cambio.
defecto el

Respuestas:

145

Porque solo hay un elenco. Compare esto:

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 esto:

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 admite una sintaxis más compacta mediante la coincidencia de patrones :

if (myObj.myProp is MyType myObjRef)
{
    ...
}
Jeff E
fuente
3
exactamente. usar 'is' es básicamente hacer algo como return ((myProp as MyType) == null)
Bambu
2
Sin embargo, en lo que respecta a los cambios, esto es bastante minucioso. La verificación nula será bastante comparable a la segunda verificación de tipo. aspuede ser un par de nanosegundos más rápido, pero considero que esto es una microoptimización prematura.
Servicio
4
También tenga en cuenta que la versión original no es segura para subprocesos. El valor de myObjo myProppodría cambiar (por otro hilo) entre el isy el elenco, provocando un comportamiento no deseado.
Jeff E
1
También podría agregar que usar as+ != nulltambién ejecutará el !=operador anulado de MyTypesi está definido (incluso si myObjRefes nulo). Si bien en la mayoría de los casos esto no es un problema (especialmente si lo implementa correctamente), en algunos casos extremos (código incorrecto, rendimiento) puede que no sea deseado. (Sin embargo, tendría que ser bastante extremo )
Chris Sinclair
1
@Chris: Correcto, se usaría la traducción correcta del código object.ReferenceEquals(null, myObjRef).
Ben Voigt
10

La mejor opción es usar la coincidencia de patrones como esa:

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
fuente
¿Cómo es exactamente este mejor que el segundo fragmento de la pregunta?
Victor Yarema
El segundo fragmento de la pregunta se refiere al uso básico de is (sin la declaración de variable) y en ese caso comprobará el tipo dos veces (una en la declaración is y otra antes del reparto)
Francesco Cattoni
6

Todavía no hay información sobre lo que realmente sucede debajo del cinturón. Echale un vistazo a éste ejemplo:

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

Esto se traduce en la siguiente 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   

Lo que importa aquí son las llamadas isinsty castclass, ambas relativamente caras. Si compara eso con la alternativa, puede ver que solo hace una isinstverificación:

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  

También vale la pena mencionar que un tipo de valor usará en unbox.anylugar de 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   

Sin embargo, tenga en cuenta que esto no se traduce necesariamente en un resultado más rápido, como podemos ver aquí . No parece haber habido mejoras desde que se hizo esa pregunta sin embargo: moldes parecen ser realizada tan rápido como lo que solía ser, pero asy linqahora son aproximadamente 3 veces más rápido.

Jeroen Vannevel
fuente
4

Advertencia de reafilado:

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

Ambos funcionarán, depende de cómo le convenga más su código. En mi caso, simplemente ignoro esa advertencia:

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

En mi código, la segunda forma es un rendimiento más largo y peor.

Tom
fuente
1
En su ejemplo del mundo real, simplemente hay un problema de diseño. Si controla los tipos, simplemente use una interfaz como IRunable. Si no tienes el control, ¿quizás podrías usarlo dynamic?
M. Mimpen
3

Para mí, esto parece depender de las probabilidades de que sea de ese tipo o no. Ciertamente, sería más eficiente hacer el yeso por adelantado si el objeto es de ese tipo la mayor parte del tiempo. Si solo es ocasionalmente de ese tipo, entonces puede ser más óptimo verificar primero con is.

El costo de crear una variable local es muy insignificante en comparación con el costo de la verificación de tipo.

La legibilidad y el alcance son los factores más importantes para mí normalmente. No estaría de acuerdo con ReSharper y usaría el operador "es" solo por esa razón; optimizar más tarde si se trata de un verdadero cuello de botella.

(Supongo que solo está usando myObj.myProp is MyTypeuna vez en esta función)

Derrick
fuente
0

También debería sugerir un segundo cambio:

(MyType)myObj.myProp

dentro

myObjRef

Esto ahorra un acceso a la propiedad y un elenco, en comparación con el código original. Pero solo es posible después de cambiar isa as.

Ben Voigt
fuente
@Por defecto: No, no lo es. Eso no significa que no esté en el código.
Ben Voigt
1
lo siento .. incomprendido. sin embargo, (MyType)arrojará una excepción si el lanzamiento falla. assolo devoluciones null.
defecto el
@Default: El reparto no fallará, porque el tipo ya ha sido verificado is(ese código está en la pregunta).
Ben Voigt
1
sin embargo, re # quiere reemplazar ese código, lo que significa que no estaría allí después del cambio sugerido.
defecto el
Yo creo que sigo La idea aquí (sólo me tomó un tiempo). ¿Quiere decir que la primera línea está en algún lugar del código y esa línea se simplificaría después de la sugerencia Re # a la segunda línea?
defecto el
0

Yo diría que esto es para hacer una versión fuertemente tipada de myObj.myProp, que es myObjRef. Esto debe usarse cuando haga referencia a este valor en el bloque, en lugar de tener que hacer un lanzamiento.

Por ejemplo, esto:

myObjRef.SomeProperty

es mejor que esto:

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