É if (items! = Null) supérfluo antes de foreach (T item nos itens)?

103

Costumo encontrar códigos como o seguinte:

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

Basicamente, a ifcondição garante que o foreachbloco será executado apenas se itemsnão for nulo. Estou me perguntando se a ifcondição é realmente necessária ou foreachse tratarei do caso items == null.

Quer dizer, posso simplesmente escrever

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

sem se preocupar se itemsé nulo ou não? A ifcondição é supérflua? Ou isso depende do tipo de itemsou talvez em Tbem?

Nawaz
fonte
1
@ resposta de kjbartel (em " stackoverflow.com/a/32134295/401246 " é a melhor solução, porque não faz: a) envolvem degradação da (mesmo quando não o desempenho null) generalizando todo o loop para o LCD de Enumerable(como a utilização ??faria ), 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 com (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

Respostas:

115

Você ainda precisa verificar se (items! = Null), caso contrário, você obterá NullReferenceException. No entanto, você pode fazer algo assim:

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

mas você pode verificar o desempenho dele. Portanto, ainda prefiro ter if (items! = Null) primeiro.

Com base na sugestão de Eric Lippert, mudei o código para:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}
Vlad Bezden
fonte
31
Ideia bonita; uma matriz vazia seria preferível porque consome menos memória e produz menos pressão de memória. Enumerable.Empty <string> seria ainda mais preferível porque armazena em cache o array vazio que gera e o reutiliza.
Eric Lippert
5
Espero que o segundo código seja mais lento. Ele degenera a sequência para um IEnumerable<T>que, por sua vez, degrada para enumerador para uma interface, tornando a iteração mais lenta. Meu teste mostrou uma degradação de fator 5 para iteração em uma matriz int.
CodesInChaos
11
@CodeInChaos: Você normalmente acha que a velocidade de enumerar uma sequência vazia é o gargalo de desempenho em seu programa?
Eric Lippert
14
Isso não reduz apenas a velocidade de enumeração da sequência vazia, mas também da sequência completa. E se a sequência for longa o suficiente, isso pode importar. Para a maioria dos códigos, devemos escolher o código idiomático. Mas as duas alocações que você mencionou serão um problema de desempenho em ainda menos casos.
CodesInChaos
15
@CodeInChaos: Ah, entendo seu ponto agora. Quando o compilador pode detectar que o "foreach" está iterando sobre um List <T> ou uma matriz, ele pode otimizar o foreach para usar enumeradores de tipo de valor ou realmente gerar um loop "for". Quando forçado a enumerar em uma lista ou em uma sequência vazia, ele precisa voltar ao codegen de "menor denominador comum", que pode em alguns casos ser mais lento e produzir mais pressão de memória. Este é um ponto sutil, mas excelente. Claro, a moral da história é - como sempre - se você tiver um problema de desempenho, então crie um perfil para descobrir qual é o verdadeiro gargalo.
Eric Lippert
68

Usando C # 6, você pode usar o novo operador condicional nulo junto com List<T>.ForEach(Action<T>) (ou seu próprioIEnumerable<T>.ForEach método de extensão).

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});
kjbartel
fonte
Resposta elegante. Obrigado!
Steve
2
Esta é a melhor solução, porque não: a) envolve degradação de desempenho de (mesmo quando não null) generalizar todo o loop para o LCD de Enumerable(como ??faria), b) requer a adição de um Método de extensão para cada projeto, ou c ) exigem evitar null IEnumerables (Pffft! Puh-LEAZE! SMH.) para começar com (cuz, nullsignifica N / A, enquanto lista vazia significa, é aplicável, mas está 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 houver ganho).
Tom,
6
@Tom: Presume que itemsseja um pensamento List<T>, e não qualquer um IEnumerable<T>. (Ou tenha um método de extensão personalizado, que você disse que não queria que houvesse ...) Além disso, eu diria que realmente não vale a pena adicionar 11 comentários, todos basicamente dizendo que você gosta de uma resposta específica.
Jon Skeet
2
@Tom: Eu o desencorajaria fortemente de fazer isso no futuro. Imagine se todos que discordaram de seu comentário adicionassem seus comentários a todos os seus . (Imagine que eu tenha escrito minha resposta aqui apenas 11 vezes.) Esse simplesmente não é um uso produtivo do Stack Overflow.
Jon Skeet
1
Eu também presumiria que haveria um impacto no desempenho ao chamar o delegado em vez de um padrão foreach. Particularmente para uma lista que eu acho que é convertida em um forloop.
kjbartel
37

A verdadeira lição aqui deve ser uma sequência quase nunca deve ser nula em primeiro lugar . Basta torná-lo uma invariante em todos os seus programas para que, se você tiver uma sequência, ela nunca seja nula. É sempre inicializado para ser a sequência vazia ou alguma outra sequência genuína.

Se uma sequência nunca for nula, obviamente você não precisa verificá-la.

Eric Lippert
fonte
1
Que tal se você obtiver a sequência de um serviço WCF? Pode ser nulo, certo?
Nawaz
4
@Nawaz: Se eu tivesse um serviço WCF que retornasse sequências nulas com a intenção de serem sequências vazias, eu relataria isso a eles como um bug. Dito isso: se você tiver que lidar com a saída mal formada de serviços possivelmente com bugs, então sim, você terá que lidar com isso verificando se há null.
Eric Lippert
7
A menos, claro, que nulo e vazio signifiquem coisas completamente diferentes. Às vezes, isso é válido para sequências.
configurador de
@Nawaz Que tal DataTable.Rows que retorna nulo em vez de uma coleção vazia. Talvez seja um bug?
Neil B de
@ resposta de kjbartel (em " stackoverflow.com/a/32134295/401246 " é a melhor solução, porque não faz: a) envolvem degradação da (mesmo quando não o desempenho null) generalizando todo o loop para o LCD de Enumerable(como a utilização ??faria ), 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 com (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
10

Na verdade, há uma solicitação de recurso nesse @Connect: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

E a resposta é bastante lógica:

Acho que a maioria dos loops foreach são escritos com a intenção de iterar uma coleção não nula. Se você tentar iterar por meio de null, deverá obter sua exceção, para que possa corrigir seu código.

Teoman Soygul
fonte
Acho que há prós e contras nisso, então eles decidiram mantê-lo como foi projetado em primeiro lugar. afinal, o foreach é apenas um açúcar sintático. se você tivesse chamado items.GetEnumerator (), isso também teria travado se items fosse nulo, então você teve que testar isso primeiro.
Marius Bancila
6

Você sempre pode testar com uma lista nula ... mas isso é o que eu encontrei no site do msdn

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

Se expression tiver o valor null, um System.NullReferenceException será lançado.

nbz
fonte
2

Não é supérfluo. No tempo de execução, os itens serão lançados em um IEnumerable e seu método GetEnumerator será chamado. Isso causará uma desreferenciação de itens que falharão

boca
fonte
1
1) A sequência não será necessariamente lançada IEnumerablee 2) É uma decisão de design fazer o lançamento. C # poderia inserir facilmente esse nullcheque se os desenvolvedores considerassem isso uma boa ideia.
CodesInChaos
2

Você pode encapsular a verificação nula em um método de extensão e usar um 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);
      }
    }
  }
}

O código se torna:

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

Pode ser ainda mais conciso se você quiser apenas chamar um método que pega um item e retorna void:

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

Você precisa disso. Você obterá uma exceção ao foreachacessar o contêiner para configurar a iteração de outra forma.

Nos bastidores, foreachusa uma interface implementada na classe de coleção para realizar a iteração. A interface genérica equivalente está aqui .

A instrução foreach da linguagem C # (para cada um no Visual Basic) esconde a complexidade dos enumeradores. Portanto, usar foreach é recomendado em vez de manipular diretamente o enumerador.

Steve Townsend
fonte
1
Apenas como uma observação, ele tecnicamente não usa a interface, ele usa a digitação de pato: blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx as interfaces garantem que os métodos e propriedades corretos estejam lá entretanto, e ajuda na compreensão da intenção. bem como usar foreach fora ...
ShuggyCoUk
0

O teste é necessário, porque se a coleção for nula, foreach lançará uma NullReferenceException. Na verdade, é muito simples de experimentar.

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

o segundo vai lançar um NullReferenceExceptioncom a mensagemObject reference not set to an instance of an object.

Harryovers
fonte
0

Conforme mencionado aqui, você precisa verificar se não é nulo.

Não use uma expressão avaliada como nula.

Renatas M.
fonte
0

Em C # 6, você pode escrever sth assim:

// 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)
{
    //..
}

É basicamente a solução de Vlad Bezden, mas usando o ?? expressão para sempre gerar uma matriz que não seja nula e, portanto, sobreviva a foreach, em vez de ter essa verificação dentro do colchete foreach.

dr. rAI
fonte