Est-ce que if (items! = Null) est superflu avant foreach (T item in items)?

104

Je rencontre souvent du code comme celui-ci:

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

Fondamentalement, la ifcondition garantit que le foreachbloc ne s'exécutera que s'il itemsn'est pas nul. Je me demande si la ifcondition est vraiment nécessaire, ouforeach va gérer le cas si items == null.

Je veux dire, puis-je simplement écrire

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

sans se soucier de savoir si itemsest nul ou non? La ifcondition est-elle superflue? Ou cela dépend du type de itemsou peut-être Taussi?

Nawaz
la source
1
La réponse de @ kjbartel (à " stackoverflow.com/a/32134295/401246 " est la meilleure solution, car elle n'implique pas: a) une dégradation des performances de (même si ce n'est pas le cas null) la généralisation de la boucle entière à l'écran LCD Enumerable(comme le ??ferait ), b) nécessitent l'ajout d'une méthode d'extension à chaque projet, ou c) nécessitent d'éviter null IEnumerables (Pffft! Puh-LEAZE! SMH.) pour commencer par (cuz, nullsignifie N / A, alors qu'une liste vide signifie, c'est appl. mais est actuellement, eh bien, vide !, c'est-à-dire qu'un employeur pourrait avoir des commissions qui sont N / A pour les non-ventes ou vides pour les ventes quand ils n'en ont pas gagné).
Tom

Réponses:

115

Vous devez toujours vérifier si (items! = Null) sinon vous obtiendrez NullReferenceException. Cependant, vous pouvez faire quelque chose comme ceci:

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

mais vous pouvez en vérifier les performances. Donc je préfère toujours avoir if (items! = Null) en premier.

Sur la base de la suggestion d'Eric Lippert, j'ai changé le code en:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}
Vlad Bezden
la source
31
Idée mignonne; un tableau vide serait préférable car il consomme moins de mémoire et produit moins de pression mémoire. Enumerable.Empty <string> serait encore plus préférable car il met en cache le tableau vide qu'il génère et le réutilise.
Eric Lippert
5
Je m'attends à ce que le deuxième code soit plus lent. Il dégénère la séquence en un IEnumerable<T>qui à son tour se dégrade en énumérateur en une interface rendant l'itération plus lente. Mon test a montré une dégradation du facteur 5 pour l'itération sur un tableau int.
CodesInChaos
11
@CodeInChaos: Trouvez-vous généralement que la vitesse d'énumération d'une séquence vide est le goulot d'étranglement des performances dans votre programme?
Eric Lippert
14
Cela ne réduit pas seulement la vitesse d'énumération de la séquence vide, mais également celle de la séquence complète. Et si la séquence est suffisamment longue, cela peut avoir de l'importance. Pour la plupart des codes, nous devons choisir le code idiomatique. Mais les deux allocations que vous avez mentionnées seront un problème de performance dans encore moins de cas.
CodesInChaos
15
@CodeInChaos: Ah, je vois votre point de vue maintenant. Lorsque le compilateur peut détecter que le "foreach" itère sur un List <T> ou un tableau, il peut alors optimiser le foreach pour utiliser des énumérateurs de type valeur ou générer en fait une boucle "for". Lorsqu'il est forcé d'énumérer sur une liste ou sur la séquence vide, il doit revenir au codegen du «plus petit dénominateur commun», qui peut dans certains cas être plus lent et produire plus de pression mémoire. C'est un point subtil mais excellent. Bien sûr, la morale de l'histoire est - comme toujours - si vous avez un problème de performance, profilez-la pour découvrir quel est le véritable goulot d'étranglement.
Eric Lippert
69

En utilisant C # 6, vous pouvez utiliser le nouvel opérateur conditionnel null avec List<T>.ForEach(Action<T>)(ou votre propre IEnumerable<T>.ForEachméthode d'extension).

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});
kjbartel
la source
Réponse élégante. Merci!
Steve
2
C'est la meilleure solution, car elle n'implique pas: a) une dégradation des performances (même si ce n'est pas le cas null) la généralisation de la boucle entière à l'écran LCD de Enumerable(comme le ??ferait l' utilisation ), b) nécessite l'ajout d'une méthode d'extension à chaque projet, ou c ) nécessitent d'éviter null IEnumerables (Pffft! Puh-LEAZE! SMH.) pour commencer par (cuz, nullsignifie N / A, alors qu'une liste vide signifie, c'est appl. mais est actuellement, eh bien, vide !, c'est-à-dire qu'un Empl. N / A pour les non-ventes ou vide pour les ventes quand ils n'en ont pas gagné).
Tom
6
@Tom: Cela suppose que itemsc'est un List<T>bien, plutôt que n'importe lequel IEnumerable<T>. (Ou ayez une méthode d'extension personnalisée, que vous avez dit que vous ne voulez pas qu'il y ait ...) De plus, je dirais que cela ne vaut vraiment pas la peine d'ajouter 11 commentaires, tous disant essentiellement que vous aimez une réponse particulière.
Jon Skeet
2
@Tom: Je vous découragerais fortement de le faire à l'avenir. Imaginez si tous ceux qui n'étaient pas d'accord avec votre commentaire ajoutaient ensuite leurs commentaires à tous les vôtres . (Imaginez que j'aie écrit ma réponse ici mais 11 fois.) Ce n'est tout simplement pas une utilisation productive de Stack Overflow.
Jon Skeet
1
Je suppose également qu'il y aurait un coup de performance appelant le délégué par rapport à une norme foreach. En particulier pour une liste qui, je pense, est convertie en forboucle.
kjbartel
37

La vraie chose à retenir ici devrait être qu'une séquence ne devrait presque jamais être nulle en premier lieu . Faites simplement en un invariant dans tous vos programmes que si vous avez une séquence, elle n'est jamais nulle. Il est toujours initialisé pour être la séquence vide ou une autre séquence authentique.

Si une séquence n'est jamais nulle, vous n'avez évidemment pas besoin de la vérifier.

Eric Lippert
la source
2
Que diriez-vous si vous obtenez la séquence d'un service WCF? Cela pourrait être nul, non?
Nawaz
4
@Nawaz: Si j'avais un service WCF qui me renvoyait des séquences nulles les destinant à être des séquences vides, alors je leur rapporterais cela comme un bogue. Cela dit: si vous devez gérer une sortie mal formée de services sans doute bogués, alors oui, vous devez le gérer en vérifiant la valeur null.
Eric Lippert
7
À moins que, bien sûr, nul et vide ne signifient des choses complètement différentes. Parfois, c'est valable pour les séquences.
configurateur
@Nawaz Que diriez-vous de DataTable.Rows qui renvoie null au lieu d'une collection vide. C'est peut-être un bug?
Neil B
La réponse de @ kjbartel (à " stackoverflow.com/a/32134295/401246 " est la meilleure solution, car elle n'implique pas: a) une dégradation des performances de (même si ce n'est pas le cas null) la généralisation de la boucle entière à l'écran LCD Enumerable(comme le ??ferait ), b) nécessitent l'ajout d'une méthode d'extension à chaque projet, ou c) nécessitent d'éviter null IEnumerables (Pffft! Puh-LEAZE! SMH.) pour commencer par (cuz, nullsignifie N / A, alors qu'une liste vide signifie, c'est appl. mais est actuellement, eh bien, vide !, c'est-à-dire qu'un employeur pourrait avoir des commissions N / A pour les non-ventes ou vides pour les ventes quand ils n'en ont pas gagné).
Tom
10

En fait, il y a une demande de fonctionnalité sur ce @Connect: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

Et la réponse est assez logique:

Je pense que la plupart des boucles foreach sont écrites dans le but d'itérer une collection non nulle. Si vous essayez d'itérer sur null, vous devriez obtenir votre exception, afin de pouvoir corriger votre code.

Teoman Soygul
la source
Je suppose qu'il y a des avantages et des inconvénients à cela, alors ils ont décidé de le garder tel qu'il avait été conçu au départ. après tout, le foreach n'est qu'un sucre syntaxique. si vous aviez appelé items.GetEnumerator () qui se serait également écrasé si items était nul, vous deviez donc d'abord le tester.
Marius Bancila le
6

Vous pouvez toujours le tester avec une liste nulle ... mais c'est ce que j'ai trouvé sur le site Web de msdn

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

Si expression a la valeur null, une exception System.NullReferenceException est levée.

nbz
la source
2

Ce n'est pas superfleux. Au moment de l'exécution, les éléments seront convertis en un IEnumerable et sa méthode GetEnumerator sera appelée. Cela entraînera un déréférencement des éléments qui échoueront

boca
la source
1
1) La séquence ne sera pas nécessairement lancée IEnumerableet 2) C'est une décision de conception de la faire lancer. C # pourrait facilement insérer cette nullvérification si les développeurs considéraient que c'était une bonne idée.
CodesInChaos
2

Vous pouvez encapsuler la vérification null dans une méthode d'extension et utiliser un 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);
      }
    }
  }
}

Le code devient:

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

Cela peut être encore plus concis si vous souhaitez simplement appeler une méthode qui prend un élément et renvoie void:

items.ForEach(MethodThatTakesAnItem);
Jordão
la source
1

Vous en avez besoin. Vous obtiendrez une exception lorsque vous foreachaccéderez au conteneur pour configurer l'itération dans le cas contraire.

Sous les couvertures, foreachutilise une interface implémentée sur la classe de collection pour effectuer l'itération. L'interface équivalente générique est ici .

L'instruction foreach du langage C # (pour chacun dans Visual Basic) masque la complexité des énumérateurs. Par conséquent, il est recommandé d'utiliser foreach au lieu de manipuler directement l'énumérateur.

Steve Townsend
la source
1
Tout comme une note, il n'utilise pas techniquement l'interface, il utilise le typage canard: blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx les interfaces garantissent que les bonnes méthodes et propriétés sont là cependant, et aide à comprendre l'intention. ainsi que d'utiliser à l'extérieur pourchaque ...
ShuggyCoUk
0

Le test est nécessaire, car si la collection est nulle, foreach lèvera une NullReferenceException. C'est en fait assez simple de l'essayer.

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

le second lancera un NullReferenceExceptionavec le messageObject reference not set to an instance of an object.

harryovers
la source
0

Comme mentionné ici, vous devez vérifier s'il n'est pas nul.

N'utilisez pas une expression évaluée à null.

Renatas M.
la source
0

En C # 6, vous pouvez écrire qc comme ceci:

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

C'est essentiellement la solution de Vlad Bezden mais en utilisant le ?? expression pour toujours générer un tableau qui n'est pas nul et qui survit donc au foreach plutôt que d'avoir cette vérification à l'intérieur du crochet foreach.

dr. rAI
la source