Compruebe si hay nulo en el bucle foreach

97

¿Existe una forma mejor de hacer lo siguiente
? Necesito una verificación para que no se produzca un valor nulo en el archivo. Encabezados antes de continuar con el bucle

if (file.Headers != null)
{
  foreach (var h in file.Headers)
  {
   //set lots of properties & some other stuff
  }
}

En resumen, parece un poco feo escribir el foreach dentro del if debido al nivel de sangría que ocurre en mi código.

Es algo que evaluaría

foreach(var h in (file.Headers != null))
{
  //do stuff
}

¿posible?

Eminem
fuente
3
Puede echar un vistazo aquí: stackoverflow.com/questions/6937407/…
Adrian Fâciu
1
@AdrianFaciu Creo que eso es completamente diferente. La pregunta comprueba si la colección es nula primero antes de hacer el for-each. Su enlace comprueba si el elemento de la colección es nulo.
rikitikitik
1
C # 8 podría tener simplemente un foreach condicional nulo de algún tipo, es decir, una sintaxis como esta: foreach? (var i en la colección) {} Creo que es un escenario lo suficientemente común como para justificar esto, y dadas las recientes adiciones condicionales nulas al lenguaje, ¿tiene sentido aquí?
mms

Respuestas:

126

Solo como una pequeña adición cosmética a la sugerencia de Rune, puede crear su propio método de extensión:

public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

Entonces puedes escribir:

foreach (var header in file.Headers.OrEmptyIfNull())
{
}

Cambie el nombre según el gusto :)

Jon Skeet
fuente
80

Suponiendo que el tipo de elementos en file.Headers es T, podría hacer esto

foreach(var header in file.Headers ?? Enumerable.Empty<T>()){
  //do stuff
}

esto creará un enumerable vacío de T si file.Headers es nulo. Sin embargo, si el tipo de archivo es de su propiedad, consideraría cambiar el captador de Headers. nulles el valor de desconocido, así que si es posible, en lugar de usar nulo como "Sé que no hay elementos" cuando nulo en realidad (/ originalmente) debe interpretarse como "No sé si hay elementos", use un conjunto vacío para mostrar que sabes que no hay elementos en el conjunto. Eso también sería DRY'er ya que no tendrá que hacer la verificación nula con tanta frecuencia.

EDITAR como seguimiento de la sugerencia de Jon, también puede crear un método de extensión cambiando el código anterior a

foreach(var header in file.Headers.OrEmptyIfNull()){
  //do stuff
}

En el caso de que no pueda cambiar el getter, este sería mi preferido, ya que expresa la intención más claramente al darle un nombre a la operación (OrEmptyIfNull)

El método de extensión mencionado anteriormente puede hacer que ciertas optimizaciones sean imposibles de detectar para el optimizador. Específicamente, aquellos que están relacionados con IList usando métodos de sobrecarga, esto se puede eliminar.

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}
Runa FS
fuente
La respuesta de @ kjbartel (en "stackoverflow.com/a/32134295/401246" es la mejor solución, porque no: a) implica la degradación del rendimiento de (incluso cuando no null) degenerar todo el bucle a LCD de IEnumerable<T>(como usar ?? would), b) requiere agregar un método de extensión a cada proyecto, o c) requiere evitar null IEnumerables(Pffft! Puh-LEAZE! SMH.) para comenzar (cuz nullsignifica N / A, mientras que la lista vacía significa, es aplicable pero actualmente, bueno, ¡vacío !, es decir, un Empleado podría tener Comisiones N / A para no Ventas o vacío para Ventas cuando no ha ganado ninguna).
Tom
@Tom, aparte de una verificación nula, no hay penalización para los casos en los que el enumerador no es nulo. Evitar esa verificación y al mismo tiempo garantizar que el enumerable no sea nulo es imposible. El código anterior requiere que los encabezados IEnumerable sean más restrictivos que los foreachrequisitos, pero menos restrictivos que los requisitos de List<T>la respuesta a la que se vincula. Que tienen la misma penalización de rendimiento que probar si el enumerable es nulo.
Rune FS
Estaba basando el problema de "LCD" en el comentario de Eric Lippert sobre la respuesta de Vlad Bezden en el mismo hilo que la respuesta de kjbartel: "@CodeInChaos: Ah, veo su punto ahora. Cuando el compilador puede detectar que el" foreach "está iterando sobre una List <T> o una matriz, entonces 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 " mínimo común denominador "codegen, que en algunos casos puede ser más lento y producir más presión de memoria ....". De acuerdo, requiere List<T>.
Tom
@tom La premisa de la respuesta es ese archivo.Los encabezados son un IEnumerable <T>, en cuyo caso el compilador no puede realizar la optimización. Sin embargo, es bastante sencillo ampliar la solución del método de extensión para evitar esto. Ver editar
Rune FS
19

Francamente, le aconsejo: simplemente aguante la nullprueba. UNAnull prueba es solo una brfalseo brfalse.s; todo lo demás va a implicar mucho más trabajo (pruebas, tareas, llamadas de método adicional innecesario GetEnumerator(), MoveNext(), Dispose()en el iterador, etc).

Un if prueba es simple, obvia y eficiente.

Marc Gravell
fuente
1
Usted hace un punto interesante, Marc, sin embargo, actualmente estoy buscando disminuir los niveles de sangría del código. Pero tendré en cuenta su comentario cuando necesite tomar nota del desempeño.
Eminem
3
Solo una nota rápida sobre este Marc ... años después de que hice esta pregunta y necesitaba implementar algunas mejoras de rendimiento, su consejo fue extremadamente útil. Gracias
Eminem
13

el "si" antes de la iteración está bien, pocas de esas semánticas "bonitas" pueden hacer que su código sea menos legible.

de todos modos, si la sangría le molesta, puede cambiar si desea comprobar:

if(file.Headers == null)  
   return;

y llegará al bucle foreach solo cuando haya un valor verdadero en la propiedad de encabezados.

otra opción en la que puedo pensar es usar el operador de fusión nula dentro de su bucle foreach y evitar por completo la verificación de nulos. muestra:

List<int> collection = new List<int>();
collection = null;
foreach (var i in collection ?? Enumerable.Empty<int>())
{
    //your code here
}

(reemplace la colección con su verdadero objeto / tipo)

Tamir
fuente
Su primera opción no funcionará si hay algún código fuera de la declaración if.
rikitikitik
Estoy de acuerdo, la declaración if es fácil y barata de implementar en comparación con la creación de una nueva lista, solo para embellecer el código.
Andreas Johansson
11

Usando el operador condicional nulo y ForEach () que funciona más rápido que el bucle foreach estándar.
Sin embargo, tienes que lanzar la colección a List.

   listOfItems?.ForEach(item => // ... );
Andrei Karcheuski
fuente
4
Agregue alguna explicación alrededor de su respuesta, indicando explícitamente por qué funciona esta solución, en lugar de solo un código de una sola línea
LordWilmore
la mejor solución para mi caso
Josef Henn
3

Estoy usando un pequeño método de extensión para estos escenarios:

  public static class Extensions
  {
    public static IList<T> EnsureNotNull<T>(this IList<T> list)
    {
      return list ?? new List<T>();
    }
  }

Dado que los encabezados son de tipo lista, puede hacer lo siguiente:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}
Wolfgang Ziegler
fuente
1
puede usar el ??operador y acortar la declaración de retorno areturn list ?? new List<T>;
Rune FS
1
@wolfgangziegler, si entiendo correctamente, la prueba de nullsu muestra file.Headers.EnsureNotNull() != nullno es necesaria, ¿e incluso es incorrecta?
Remko Jansen
0

Para algunos casos, preferiría una variante genérica ligeramente diferente, asumiendo que, como regla, los constructores de colección predeterminados devuelven instancias vacías.

Sería mejor nombrar este método NewIfDefault. Puede ser útil no solo para colecciones, por lo que la restricción de tipo IEnumerable<T>puede ser redundante.

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
N. Kudryavtsev
fuente