La respuesta debajo de la línea fue escrita en 2008.
C # 7 introdujo la coincidencia de patrones, que ha reemplazado en gran medida al as
operador, como ahora puede escribir:
if (randomObject is TargetType tt)
{
// Use tt here
}
Tenga en cuenta que tt
todavía está dentro del alcance después de esto, pero no está definitivamente asignado. ( Definitivamente está asignado dentro del if
cuerpo). Eso es un poco molesto en algunos casos, por lo que si realmente te importa introducir la menor cantidad de variables posibles en cada ámbito, es posible que aún quieras usar is
seguido de un reparto.
No creo que ninguna de las respuestas hasta ahora (en el momento de comenzar esta respuesta) haya explicado realmente dónde vale la pena usar cuál.
No hagas esto:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
Esto no solo está comprobando dos veces, sino que puede estar comprobando cosas diferentes, si randomObject
es un campo en lugar de una variable local. Es posible que el "si" pase pero luego el lanzamiento falle, si otro hilo cambia el valor de randomObject
entre los dos.
Si randomObject
realmente debería ser una instancia de TargetType
, es decir, si no lo es, eso significa que hay un error, entonces lanzar es la solución correcta. Eso arroja una excepción de inmediato, lo que significa que no se realiza más trabajo bajo suposiciones incorrectas, y la excepción muestra correctamente el tipo de error.
// This will throw an exception if randomObject is non-null and
// refers to an object of an incompatible type. The cast is
// the best code if that's the behaviour you want.
TargetType convertedRandomObject = (TargetType) randomObject;
Si randomObject
podría ser una instancia de TargetType
y TargetType
es un tipo de referencia, utilice un código como este:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
Si randomObject
podría ser una instancia de TargetType
y TargetType
es un tipo de valor, entonces no podemos usarlo as
consigo TargetType
mismo, pero podemos usar un tipo anulable:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(Nota: actualmente esto es en realidad más lento que el reparto + . Creo que es más elegante y consistente, pero ahí vamos).
Si realmente no necesita el valor convertido, pero solo necesita saber si es una instancia de TargetType, entonces el is
operador es su amigo. En este caso, no importa si TargetType es un tipo de referencia o un tipo de valor.
Puede haber otros casos que involucren genéricos donde is
sea útil (porque es posible que no sepa si T es un tipo de referencia o no, por lo que no puede usar como), pero son relativamente oscuros.
Es casi seguro que lo he usado antes is
para el caso del tipo de valor, sin haber pensado en usar un tipo anulable y as
juntos :)
EDITAR: tenga en cuenta que ninguno de los anteriores habla sobre el rendimiento, excepto el caso del tipo de valor, donde he notado que unboxing a un tipo de valor anulable es en realidad más lento, pero consistente.
De acuerdo con la respuesta de naasking, is-and-cast o is-and-as son tan rápidos como el chequeo nulo con los JIT modernos, como se muestra en el siguiente código:
using System;
using System.Diagnostics;
using System.Linq;
class Test
{
const int Size = 30000000;
static void Main()
{
object[] values = new object[Size];
for (int i = 0; i < Size - 2; i += 3)
{
values[i] = null;
values[i + 1] = "x";
values[i + 2] = new object();
}
FindLengthWithIsAndCast(values);
FindLengthWithIsAndAs(values);
FindLengthWithAsAndNullCheck(values);
}
static void FindLengthWithIsAndCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = (string) o;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and Cast: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithIsAndAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = o as string;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and As: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithAsAndNullCheck(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
string a = o as string;
if (a != null)
{
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("As and null check: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
}
En mi computadora portátil, todo esto se ejecuta en aproximadamente 60 ms. Dos cosas a tener en cuenta:
- No hay diferencia significativa entre ellos. (De hecho, hay situaciones en las que la verificación as-plus-null definitivamente es más lenta. El código anterior en realidad facilita la verificación de tipo porque es para una clase sellada; si está buscando una interfaz, la balanza se inclina ligeramente a favor de as-plus-null-check.)
- Todos son increíblemente rápidos. Esto simplemente no será el cuello de botella en su código a menos que realmente no vaya a hacer nada con los valores después.
Así que no nos preocupemos por el rendimiento. Preocupémonos por la corrección y la consistencia.
Sostengo que is-and-cast (o is-and-as) no son seguros cuando se trata de variables, ya que el tipo del valor al que se refiere puede cambiar debido a otro hilo entre la prueba y el elenco. Esa sería una situación bastante rara, pero prefiero tener una convención que pueda usar de manera consistente.
También sostengo que el cheque nulo como entonces da una mejor separación de las preocupaciones. Tenemos una declaración que intenta una conversión, y luego una declaración que usa el resultado. Is-and-cast o is-and-as realiza una prueba y luego otro intento de convertir el valor.
Para decirlo de otra manera, ¿alguien escribiría alguna vez :
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
Eso es algo de lo que está haciendo el reparto, aunque obviamente de una manera bastante más barata.