Vérifier la valeur nulle dans la boucle foreach

91

Existe-t-il un moyen plus agréable de faire ce qui suit:
J'ai besoin d'une vérification de la présence de null sur le fichier.Headers avant de poursuivre la boucle

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

En bref, cela semble un peu moche d'écrire le foreach à l'intérieur du if en raison du niveau d'indentation qui se produit dans mon code.

Est-ce quelque chose qui serait évalué à

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

possible?

Eminem
la source
3
Vous pouvez jeter un œil ici: stackoverflow.com/questions/6937407/…
Adrian Fâciu
stackoverflow.com/questions/872323/… est une autre idée.
weismat
1
@AdrianFaciu Je pense que c'est complètement différent. La question vérifie si la collection est nulle avant de faire le for-each. Votre lien vérifie si l'élément de la collection est nul.
rikitikitik
1
C # 8 pourrait simplement avoir un foreach conditionnel nul, c'est-à-dire une syntaxe comme celle-ci: foreach? (var i in collection) {} Je pense que c'est un scénario assez courant pour justifier cela, et étant donné les récents ajouts conditionnels null au langage, cela a du sens ici?
mms

Réponses:

121

Juste comme un léger ajout cosmétique à la suggestion de Rune, vous pouvez créer votre propre méthode d'extension:

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

Ensuite, vous pouvez écrire:

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

Changez le nom selon vos goûts :)

Jon Skeet
la source
75

En supposant que le type d'éléments dans file.Headers est T, vous pouvez le faire

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

cela créera un énumérable vide de T si file.Headers est nul. Si le type de fichier est un type que vous possédez, j'envisagerais cependant de changer le getter à la Headersplace. nullest la valeur de unknown donc si possible au lieu d'utiliser null comme "Je sais qu'il n'y a pas d'éléments" alors que null en fait (/ à l'origine) devrait être interprété comme "Je ne sais pas s'il y a des éléments", utilisez un ensemble vide pour afficher que vous savez qu'il n'y a aucun élément dans l'ensemble. Ce serait également DRY'er puisque vous n'aurez pas à faire la vérification de null aussi souvent.

EDIT comme suite à la suggestion de Jons, vous pouvez également créer une méthode d'extension en changeant le code ci-dessus en

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

Dans le cas où vous ne pouvez pas changer le getter, ce serait mon préféré car il exprime l'intention plus clairement en donnant un nom à l'opération (OrEmptyIfNull)

La méthode d'extension mentionnée ci-dessus peut rendre certaines optimisations impossibles à détecter par l'optimiseur. Plus précisément, ceux qui sont liés à IList en utilisant une surcharge de méthode peuvent être éliminés

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}
Rune FS
la source
La réponse de @ kjbartel (sur "stackoverflow.com/a/32134295/401246" est la meilleure solution, car elle n'implique pas: a) une dégradation des performances (même si non null) dégénérant la boucle entière vers LCD de IEnumerable<T>(comme l'utilisation de ?? aurait), b) exigerait d'ajouter une méthode d'extension à chaque projet, ou c) exigerait d'éviter null IEnumerables(Pffft! Puh-LEAZE! SMH.) pour commencer (cuz nullsignifie N / A, alors qu'une liste vide signifie, c'est applicable mais est actuellement, enfin, vide !, c'est-à-dire qu'un employé pourrait avoir des commissions N / A pour les non-ventes ou vides pour les ventes alors qu'il n'en a pas gagné).
Tom
@Tom à part une vérification nulle, il n'y a aucune pénalité pour les cas où l'énumérateur n'est pas nul. Il est impossible d'éviter cette vérification tout en s'assurant que l'énumérable n'est pas nul. Le code ci-dessus nécessite les en-têtes vers un an IEnumerable qui est plus restrictif que les foreachexigences mais moins restrictif que l'exigence de List<T>dans la réponse à laquelle vous créez un lien. Qui ont la même pénalité de performances de tester si l'énumérable est nul.
Rune FS
Je basais le problème "LCD" sur le commentaire d'Eric Lippert sur la réponse de Vlad Bezden dans le même fil de discussion que la réponse de kjbartel: "@CodeInChaos: Ah, je vois votre point maintenant. Quand le compilateur peut détecter que le" foreach "est en cours d'itération 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 " plus petit dénominateur commun "codegen, qui peut dans certains cas être plus lent et produire plus de pression mémoire ....". D'accord, il faut List<T>.
Tom
@tom La prémisse de la réponse est que file.Headers sont un IEnumerable <T> auquel cas le compilateur ne peut pas faire l'optimisation. Il est cependant assez simple d'étendre la solution de la méthode d'extension pour éviter cela. Voir modifier
Rune FS
19

Franchement, je conseille: il suffit d'aspirer null test. Un nulltest est juste un brfalseou brfalse.s; tout le reste va impliquer beaucoup plus de travail (tests, devoirs, appels de méthodes supplémentaires, inutiles GetEnumerator(), MoveNext(),Dispose() sur le iterator, etc.).

Un iftest est simple, évident et efficace.

Marc Gravell
la source
1
Vous faites un point intéressant Marc, cependant, je cherche actuellement à réduire les niveaux d'indentation du code. Mais je garderai votre commentaire à l'esprit lorsque j'aurai besoin de prendre note des performances.
Eminem
3
Juste un petit mot sur ce Marc ... des années après avoir posé cette question et avoir besoin de mettre en œuvre des améliorations de performances, vos conseils m'ont été extrêmement utiles. Merci
Eminem
12

le "si" avant l'itération est correct, peu de ces "jolies" sémantiques peuvent rendre votre code moins lisible.

de toute façon, si l'indentation vous dérange, vous pouvez changer le if pour vérifier:

if(file.Headers == null)  
   return;

et vous n'obtiendrez la boucle foreach que s'il y a une valeur vraie dans la propriété headers.

une autre option à laquelle je peux penser consiste à utiliser l'opérateur de fusion null dans votre boucle foreach et d'éviter complètement la vérification null. échantillon:

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

(remplacez la collection par votre véritable objet / type)

Tamir
la source
Votre première option ne fonctionnera pas s'il y a du code en dehors de l'instruction if.
rikitikitik
Je suis d'accord, l'instruction if est facile et peu coûteuse à mettre en œuvre par rapport à la création d'une nouvelle liste, juste pour l'embellissement du code.
Andreas Johansson
10

Utilisation de l' opérateur conditionnel Null et de ForEach () qui fonctionne plus rapidement que la boucle foreach standard.
Vous devez cependant convertir la collection dans List.

   listOfItems?.ForEach(item => // ... );
Andrei Karcheuski
la source
4
Veuillez ajouter une explication autour de votre réponse, en indiquant explicitement pourquoi cette solution fonctionne, plutôt qu'un simple code one-liner
LordWilmore
meilleure solution pour mon cas
Josef Henn
3

J'utilise une jolie petite méthode d'extension pour ces scénarios:

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

Étant donné que les en-têtes sont de type liste, vous pouvez faire ce qui suit:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}
Wolfgang Ziegler
la source
1
vous pouvez utiliser l' ??opérateur et raccourcir l'instruction de retour àreturn list ?? new List<T>;
Rune FS
1
@wolfgangziegler, si je comprends bien, le test de nullvotre échantillon file.Headers.EnsureNotNull() != nulln'est pas nécessaire, et est même faux?
Remko Jansen
0

Dans certains cas, je préférerais légèrement une autre variante générique, en supposant que, en règle générale, les constructeurs de collection par défaut renvoient des instances vides.

Il serait préférable de nommer cette méthode NewIfDefault. Cela peut être utile non seulement pour les collections, donc la contrainte de type IEnumerable<T>est peut-être redondante.

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