web-dev-qa-db-fra.com

Filtrage des boucles foreach avec une condition where vs clause de garde continue

J'ai vu certains programmeurs utiliser ceci:

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

au lieu de celui que j'utiliserais normalement:

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

J'ai même vu une combinaison des deux. J'aime vraiment la lisibilité avec "continuer", en particulier avec des conditions plus complexes. Y a-t-il même une différence de performances? Avec une requête de base de données, je suppose qu'il y en aurait. Et les listes régulières?

24
Paprik

Je considérerais cela comme un endroit approprié pour utiliser séparation commande/requête . Par exemple:

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

Cela vous permet également de donner un bon nom auto-documenté au résultat de la requête. Il vous aide également à voir les opportunités de refactoring, car il est beaucoup plus facile de refactoriser du code qui interroge uniquement des données ou ne mute que des données que du code mixte qui essaie de faire les deux.

Lors du débogage, vous pouvez interrompre avant foreach pour vérifier rapidement si le contenu de validItems correspond à ce que vous attendez. Vous n'avez pas à entrer dans le lambda, sauf si vous en avez besoin. Si vous devez entrer dans le lambda, je suggère de le factoriser dans une fonction distincte, puis de le parcourir à la place.

Y a-t-il une différence de performances? Si la requête est soutenue par une base de données, alors la version LINQ a le potentiel de s'exécuter plus rapidement, car la requête SQL peut être plus efficace. S'il s'agit de LINQ to Objects, vous ne verrez aucune différence réelle de performances. Comme toujours, profilez votre code et corrigez les goulots d'étranglement qui sont réellement signalés, plutôt que d'essayer de prévoir les optimisations à l'avance.

65
Christian Hayter

Bien sûr, il existe une différence de performances, .Where() entraîne un appel délégué pour chaque élément. Cependant, je ne me soucierais pas du tout des performances:

  • Les cycles d'horloge utilisés pour appeler un délégué sont négligeables par rapport aux cycles d'horloge utilisés par le reste du code qui itère sur la collection et vérifie les conditions.

  • La pénalité de performance d'invoquer un délégué est de l'ordre de quelques cycles d'horloge, et heureusement, nous avons longtemps dépassé les jours où nous devions nous soucier des cycles d'horloge individuels.

Si, pour une raison quelconque, les performances sont vraiment importantes pour vous au niveau du cycle d'horloge, utilisez List<Item> Au lieu de IList<Item>, Afin que le compilateur puisse utiliser direct ( et inlinable) au lieu d'appels virtuels, et de sorte que l'itérateur de List<T>, qui est en fait un struct, n'a pas besoin d'être encadré. Mais c'est vraiment des trucs insignifiants.

Une requête de base de données est une situation différente, car il y a (au moins en théorie) une possibilité d'envoyer le filtre au SGBDR, améliorant ainsi considérablement les performances: seules les lignes correspondantes feront le trajet du SGBDR vers votre programme. Mais pour cela, je pense que vous devrez utiliser linq, je ne pense pas que cette expression puisse être envoyée au SGBDR tel quel.

Vous verrez vraiment les avantages de if(x) continue; au moment où vous devez déboguer ce code: Un pas sur if() s et continues fonctionne bien; une seule étape dans le délégué de filtrage est une douleur.

7
Mike Nakis