¿Es if (items! = Null) superfluo antes de foreach (item T in items)?

103

A menudo me encuentro con un código como el siguiente:

if ( items != null)
{
   foreach(T item in items)
   {
        //...
   }
}

Básicamente, la ifcondición asegura que el foreachbloque se ejecutará solo si itemsno es nulo. Me pregunto si la ifcondición es realmente necesaria, o foreachmanejaré el caso si items == null.

Quiero decir, puedo simplemente escribir

foreach(T item in items)
{
    //...
}

sin preocuparse de si itemses nulo o no? ¿La ifcondición es superflua? ¿O esto depende del tipo de itemso quizás Ttambién?

Nawaz
fuente
1
La respuesta de @ kjbartel (en " stackoverflow.com/a/32134295/401246 " es la mejor solución, porque no: a) implica una degradación del rendimiento de (incluso cuando no null) generalizar todo el ciclo a la pantalla LCD de Enumerable(como lo ??haría el uso ), b) requieren agregar un método de extensión a cada proyecto, o c) requieren evitar null IEnumerables (Pffft! Puh-LEAZE! SMH.) para comenzar con (cuz, nullsignifica N / A, mientras que la lista vacía significa, es aplicable pero es actualmente, bueno, ¡ vacío !, es decir, un Empl. podría tener comisiones que son N / A para no ventas o vacío para ventas cuando no han ganado ninguna).
Tom

Respuestas:

115

Aún debe verificar si (items! = Null); de lo contrario, obtendrá NullReferenceException. Sin embargo, puede hacer algo como esto:

List<string> items = null;  
foreach (var item in items ?? new List<string>())
{
    item.Dump();
}

pero puede comprobar su rendimiento. Así que todavía prefiero tener if (items! = Null) primero.

Según la sugerencia de Lippert de Eric, cambié el código a:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}
Vlad Bezden
fuente
31
Linda idea; sería preferible una matriz vacía porque consume menos memoria y produce menos presión de memoria. Enumerable.Empty <string> sería aún más preferible porque almacena en caché la matriz vacía que genera y la reutiliza.
Eric Lippert
5
Espero que el segundo código sea más lento. Degenera la secuencia a una IEnumerable<T>que a su vez se degrada a enumerador a una interfaz que hace que la iteración sea más lenta. Mi prueba mostró una degradación de factor 5 para la iteración sobre una matriz int.
CodesInChaos
11
@CodeInChaos: ¿Sueles encontrar que la velocidad de enumerar una secuencia vacía es el cuello de botella en el rendimiento de su programa?
Eric Lippert
14
No solo reduce la velocidad de enumeración de la secuencia vacía, sino también de la secuencia completa. Y si la secuencia es lo suficientemente larga, puede ser importante. Para la mayoría de los códigos, debemos elegir el código idiomático. Pero las dos asignaciones que mencionó serán un problema de rendimiento en incluso menos casos.
CodesInChaos
15
@CodeInChaos: Ah, ahora veo su punto. Cuando el compilador puede detectar que el "foreach" está iterando sobre una List <T> o una matriz, puede optimizar el foreach para usar enumeradores de tipo valor o generar un bucle "for". Cuando se le obliga a enumerar sobre una lista o la secuencia vacía , tiene que volver al codegen del "mínimo común denominador", que en algunos casos puede ser más lento y producir más presión de memoria. Este es un punto sutil pero excelente. Por supuesto, la moraleja de la historia es, como siempre, que si tienes un problema de rendimiento, perfílalo para descubrir cuál es el verdadero cuello de botella.
Eric Lippert
68

Usando C # 6, podría usar el nuevo operador condicional nulo junto con List<T>.ForEach(Action<T>)(o su propio IEnumerable<T>.ForEachmétodo de extensión).

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});
kjbartel
fuente
Respuesta elegante. ¡Gracias!
Steve
2
Esta es la mejor solución, porque no: a) implica una degradación del rendimiento de (incluso cuando no null) generalizar todo el ciclo a la pantalla LCD de Enumerable(como lo ??haría el uso ), b) requiere agregar un método de extensión a cada proyecto, o c ) requieren evitar null IEnumerables (Pffft! Puh-LEAZE! SMH.) para comenzar con (cuz, nullsignifica N / A, mientras que la lista vacía significa que es aplicable pero actualmente, bueno, ¡está vacío !, es decir, un Empl. podría tener Comisiones que son N / A para no ventas o vacío para ventas cuando no han ganado).
Tom
6
@Tom: Asume que itemses un pensamiento List<T>, en lugar de cualquiera IEnumerable<T>. (O tenga un método de extensión personalizado, que ha dicho que no quiere que haya ...) Además, yo diría que realmente no vale la pena agregar 11 comentarios, todos básicamente diciendo que le gusta una respuesta en particular.
Jon Skeet
2
@Tom: Te recomiendo encarecidamente que no lo hagas en el futuro. Imagínese si todos los que no estuvieron de acuerdo con su comentario añadieran sus comentarios a todos los suyos . (Imagínese que hubiera escrito mi respuesta aquí, pero 11 veces). Este simplemente no es un uso productivo de Stack Overflow.
Jon Skeet
1
También supongo que habría un impacto en el rendimiento llamando al delegado frente a un estándar foreach. Particularmente para una lista que creo que se convierte en un forbucle.
kjbartel
37

La conclusión real aquí debería ser una secuencia que casi nunca debería ser nula en primer lugar . Simplemente conviértalo en un invariante en todos sus programas que si tiene una secuencia, nunca es nulo. Siempre se inicializa para que sea la secuencia vacía o alguna otra secuencia genuina.

Si una secuencia nunca es nula, obviamente no es necesario verificarla.

Eric Lippert
fuente
1
¿Qué tal si obtiene la secuencia de un servicio WCF? Podría ser nulo, ¿verdad?
Nawaz
4
@Nawaz: Si tuviera un servicio WCF que me devolviera secuencias nulas con la intención de que fueran secuencias vacías, les informaría de eso como un error. Dicho esto: si tiene que lidiar con una salida mal formada de servicios posiblemente con errores, entonces sí, tiene que lidiar con eso verificando que no haya valores nulos.
Eric Lippert
7
A menos, por supuesto, que nulo y vacío signifiquen cosas completamente diferentes. A veces eso es válido para secuencias.
configurador
@Nawaz ¿Qué tal DataTable.Rows que devuelve nulo en lugar de una colección vacía? ¿Quizás eso es un error?
Neil B
La respuesta de @ kjbartel (en " stackoverflow.com/a/32134295/401246 " es la mejor solución, porque no: a) implica una degradación del rendimiento de (incluso cuando no null) generalizar todo el ciclo a la pantalla LCD de Enumerable(como lo ??haría el uso ), b) requieren agregar un método de extensión a cada proyecto, o c) requieren evitar null IEnumerables (Pffft! Puh-LEAZE! SMH.) para comenzar con (cuz, nullsignifica N / A, mientras que la lista vacía significa, es aplicable pero es actualmente, bueno, ¡ vacío !, es decir, un Empl. podría tener comisiones que son N / A para no ventas o vacío para ventas cuando no han ganado ninguna).
Tom
10

En realidad, hay una solicitud de función en ese @Connect: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

Y la respuesta es bastante lógica:

Creo que la mayoría de los bucles foreach se escriben con la intención de iterar una colección no nula. Si intenta iterar a través de null, debería obtener su excepción, para que pueda corregir su código.

Teoman Soygul
fuente
Supongo que esto tiene ventajas y desventajas, por lo que decidieron mantenerlo tal como fue diseñado en primer lugar. después de todo, el foreach es solo un azúcar sintáctico. si hubiera llamado items.GetEnumerator (), eso también se habría bloqueado si los elementos fueran nulos, por lo que primero tenía que probar eso.
Marius Bancila
6

Siempre puede probarlo con una lista nula ... pero esto es lo que encontré en el sitio web de msdn

foreach-statement:
    foreach   (   type   identifier   in   expression   )   embedded-statement 

Si la expresión tiene el valor nulo, se lanza una excepción System.NullReferenceException.

nbz
fuente
2

No es superfluo. En tiempo de ejecución, los elementos se convertirán en un IEnumerable y se llamará a su método GetEnumerator. Eso provocará una desreferenciación de elementos que fallarán

boca
fuente
1
1) La secuencia no necesariamente se lanzará a IEnumerabley 2) Es una decisión de diseño hacerla lanzar. C # podría insertar fácilmente ese nullcheque si los desarrolladores lo consideraran una buena idea.
CodesInChaos
2

Puede encapsular la verificación nula en un método de extensión y usar una lambda:

public static class EnumerableExtensions {
  public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
    if (self != null) {
      foreach (var element in self) {
        action(element);
      }
    }
  }
}

El código se convierte en:

items.ForEach(item => { 
  ...
});

Si puede ser aún más conciso si solo desea llamar a un método que toma un elemento y devuelve void:

items.ForEach(MethodThatTakesAnItem);
Jordão
fuente
1

Necesitas esto. Obtendrá una excepción cuando foreachacceda al contenedor para configurar la iteración de lo contrario.

Debajo de las cubiertas, foreachutiliza una interfaz implementada en la clase de colección para realizar la iteración. La interfaz equivalente genérica está aquí .

La instrucción foreach del lenguaje C # (para cada uno en Visual Basic) oculta la complejidad de los enumeradores. Por lo tanto, se recomienda utilizar foreach en lugar de manipular directamente el enumerador.

Steve Townsend
fuente
1
Solo como una nota, técnicamente no usa la interfaz, usa la escritura pato: blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx las interfaces aseguran que los métodos y propiedades correctos estén allí aunque, y ayuda a comprender la intención. así como utilizar foreach exterior ...
ShuggyCoUk
0

La prueba es necesaria, porque si la colección es nula, foreach lanzará una NullReferenceException. De hecho, es bastante sencillo probarlo.

List<string> items = null;
foreach(var item in items)
{
   Console.WriteLine(item);
}
Marius Bancila
fuente
0

el segundo lanzará un NullReferenceExceptioncon el mensajeObject reference not set to an instance of an object.

harryovers
fuente
0

Como se mencionó aquí , debe verificar si no es nulo.

No utilice una expresión que se evalúe como nula.

Renatas M.
fuente
0

En C # 6 puedes escribir algo como esto:

// some string from file or UI, i.e.:
// a) string s = "Hello, World!";
// b) string s = "";
// ...
var items = s?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable.Empty<string>();  
foreach (var item in items)
{
    //..
}

Es básicamente la solución de Vlad Bezden, pero usando el ?? expresión para generar siempre una matriz que no sea nula y, por lo tanto, sobreviva al foreach en lugar de tener esta comprobación dentro del corchete foreach.

Dr. RAI
fuente