Verifique se há nulo no loop foreach

91

Existe uma maneira mais agradável de fazer o seguinte:
Preciso que uma verificação de null aconteça no arquivo. Cabeçalhos antes de prosseguir com o loop

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

Resumindo, parece um pouco feio escrever o foreach dentro do if devido ao nível de indentação que ocorre no meu código.

É algo que avaliaria

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

possível?

Eminem
fonte
3
Você pode dar uma olhada aqui: stackoverflow.com/questions/6937407/…
Adrian Fâciu
stackoverflow.com/questions/872323/… é outra ideia.
weismat
1
@AdrianFaciu Acho que é completamente diferente. A questão verifica se a coleção é nula antes de fazer o for-each. Seu link verifica se o item da coleção é nulo.
rikitikitik
1
C # 8 poderia simplesmente ter um foreach condicional nulo de algum tipo, ou seja, sintaxe como esta: foreach? (var i in collection) {} Acho que é um cenário comum o suficiente para justificar isso e, dadas as adições condicionais nulas recentes à linguagem, faz sentido aqui?
mms de

Respostas:

121

Apenas como uma pequena adição estética à sugestão de Rune, você pode criar seu próprio método de extensão:

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

Então você pode escrever:

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

Mude o nome de acordo com o gosto :)

Jon Skeet
fonte
75

Supondo que o tipo de elementos no arquivo. Cabeçalhos é T, você poderia fazer isso

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

isso criará um enumerável vazio de T se file.Headers for null. Se o tipo de arquivo for um tipo seu, eu, entretanto, consideraria alterar o getter de Headers. nullé o valor de desconhecido, então, se possível, em vez de usar nulo como "Eu sei que não há elementos", quando nulo na verdade (/ originalmente) deve ser interpretado como "Não sei se há algum elemento", use um conjunto vazio para mostrar que você sabe que não há elementos no conjunto. Isso também seria mais seco, pois você não terá que fazer a verificação de nulos com tanta frequência.

EDITAR seguindo a sugestão de Jons, você também pode criar um método de extensão alterando o código acima para

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

No caso em que você não pode alterar o getter, seria a minha preferência, pois expressa a intenção de forma mais clara, dando um nome à operação (OrEmptyIfNull)

O método de extensão mencionado acima pode tornar certas otimizações impossíveis para o otimizador detectar. Especificamente, aqueles que estão relacionados a IList usando a sobrecarga de método podem ser eliminados

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}
Rune FS
fonte
A resposta de @kjbartel (em "stackoverflow.com/a/32134295/401246" é a melhor solução, porque não: a) envolve degradação de desempenho de (mesmo quando não null) degenerar todo o loop para LCD de IEnumerable<T>(como usando ?? seria), b) requerer a adição de um Método de Extensão para cada Projeto, ou c) Requer evitar null IEnumerables(Pffft! Puh-LEAZE! SMH.) para começar (cuz nullsignifica N / A, enquanto lista vazia significa, é aplicável, mas é atualmente, bem, vazio !, ou seja, um funcionário pode ter comissões que são N / A para não vendas ou vazio para vendas quando não ganhou nenhuma).
Tom
@Tom além de uma verificação nula, não há penalidade para os casos em que o enumerador não é nulo. É impossível evitar essa verificação e, ao mesmo tempo, garantir que o enumerável não seja nulo. O código acima requer os cabeçalhos para um IEnumerable que é mais restritivo do que os foreachrequisitos, mas menos restritivo do que o requisito de List<T>na resposta para a qual você vincula. Que têm a mesma penalidade de desempenho de testar se o enumerável é nulo.
Rune FS
Eu estava baseando a questão "LCD" no comentário de Eric Lippert sobre a resposta de Vlad Bezden no mesmo tópico da resposta de kjbartel: "@CodeInChaos: Ah, entendo seu ponto agora. Quando o compilador puder detectar que o" foreach "está iterando sobre um List <T> ou uma matriz, então ele pode otimizar o foreach para usar enumeradores de tipo de valor ou realmente gerar um loop "for". Quando forçado a enumerar sobre uma lista ou uma sequência vazia, ele deve voltar para o " menor denominador comum "codegen, que pode em alguns casos ser mais lento e produzir mais pressão de memória ...". Concordo que requerList<T> .
Tom
@tom A premissa da resposta é que file.Headers são um IEnumerable <T>, caso em que o compilador não pode fazer a otimização. No entanto, é bastante simples estender a solução do método de extensão para evitar isso. Ver editar
Rune FS
19

Francamente, eu aconselho: apenas absorva o nullteste. Um nullteste é apenas um brfalseou brfalse.s; tudo o resto vai envolver muito mais trabalho (exames, atribuições, chamadas de método extra, desnecessário GetEnumerator(), MoveNext(), Dispose()no iterador, etc).

Um ifteste é simples, óbvio e eficiente.

Marc Gravell
fonte
1
Você faz um ponto interessante Marc, no entanto, estou atualmente procurando diminuir os níveis de indentação do código. Mas vou manter seu comentário em mente quando precisar anotar o desempenho.
Eminem
3
Apenas uma nota rápida sobre isso Marc ... anos depois de fazer esta pergunta e precisar implementar algumas melhorias de desempenho, seu conselho foi extremamente útil. Obrigado
Eminem
12

o "se" antes da iteração está bom, poucas dessas semânticas "bonitas" podem tornar seu código menos legível.

de qualquer forma, se o recuo atrapalhar você, você pode alterar o if para verificar:

if(file.Headers == null)  
   return;

e você obterá o loop foreach apenas quando houver um valor verdadeiro na propriedade headers.

outra opção que posso pensar é usar o operador de coalescência nula dentro de seu loop foreach e evitar completamente a verificação de nulos. amostra:

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

(substitua a coleção pelo seu verdadeiro objeto / tipo)

Tamir
fonte
Sua primeira opção não funcionará se houver algum código fora da instrução if.
rikitikitik
Eu concordo, a instrução if é fácil e barata de implementar em comparação com a criação de uma nova lista, apenas para embelezamento do código.
Andreas Johansson
10

Usando o operador nulo condicional e ForEach () que funciona mais rápido do que o loop foreach padrão.
No entanto, você deve lançar a coleção para List.

   listOfItems?.ForEach(item => // ... );
Andrei Karcheuski
fonte
4
Adicione alguma explicação em torno de sua resposta, declarando explicitamente por que essa solução funciona, em vez de apenas um código de uma linha
LordWilmore
melhor solução para o meu caso
Josef Henn
3

Estou usando um pequeno método de extensão agradável para estes cenários:

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

Dado que Cabeçalhos é do tipo lista, você pode fazer o seguinte:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}
Wolfgang Ziegler
fonte
1
você pode usar o ??operador e encurtar a instrução de retorno parareturn list ?? new List<T>;
Rune FS
1
@wolfgangziegler, Se bem entendi o teste para nullem sua amostra file.Headers.EnsureNotNull() != nullnão é necessário, e está até errado?
Remko Jansen
0

Para alguns casos, eu preferiria um pouco outra variante genérica, supondo que, como regra, os construtores de coleção padrão retornam instâncias vazias.

Seria melhor nomear este método NewIfDefault. Pode ser útil não apenas para coleções, portanto, a restrição de tipo IEnumerable<T>pode ser redundante.

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