É 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?



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();
}

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

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.


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.


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.


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


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);

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.


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);
}

0

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


0

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

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


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.