Lorsque j'écris du code dans Visual Studio, ReSharper (que Dieu le bénisse!) Me suggère souvent de changer mon boucle old-school dans le format plus compact foreach.
Et souvent, quand j'accepte ce changement, ReSharper fait un pas en avant et me propose de le changer à nouveau, sous une forme LINQ brillante.
Je me demande donc: ces améliorations présentent-elles de réels avantages? En exécution de code assez simple, je ne vois aucune augmentation de vitesse (évidemment), mais je peux voir le code devenir de moins en moins lisible ... Je me demande donc: est-ce que cela en vaut la peine?
foreach
consiste à supprimer des éléments d'une collection tout en les énumérant, où unefor
boucle est généralement nécessaire pour commencer à partir du dernier élément.Réponses:
for
contre.foreach
Il y a une confusion commune sur le fait que ces deux constructions sont très similaires et que les deux sont interchangeables comme ceci:
et:
Le fait que les deux mots-clés commencent par les trois mêmes lettres ne signifie pas que, sémantiquement, ils sont similaires. Cette confusion est extrêmement sujette aux erreurs, en particulier pour les débutants. Itérer dans une collection et faire quelque chose avec les éléments est fait avec
foreach
;for
ne doit pas et ne doit pas être utilisé à cette fin , sauf si vous savez vraiment ce que vous faites.Voyons ce qui ne va pas avec un exemple. À la fin, vous trouverez le code complet d'une application de démonstration utilisée pour rassembler les résultats.
Dans l'exemple, nous chargeons certaines données de la base de données, plus précisément les villes d'Adventure Works, classées par nom, avant de rencontrer "Boston". La requête SQL suivante est utilisée:
Les données sont chargées par la
ListCities()
méthode qui retourne unIEnumerable<string>
. Voici à quoiforeach
ressemble:Réécrivons-le avec un
for
, en supposant que les deux sont interchangeables:Tous deux retournent les mêmes villes, mais il y a une différence énorme.
foreach
,ListCities()
est appelé une fois et donne 47 articles.for
,ListCities()
est appelé 94 fois et donne 28153 articles en tout.Qu'est-il arrivé?
IEnumerable
est paresseux . Cela signifie qu'il ne fera le travail qu'au moment où le résultat est nécessaire. L'évaluation paresseuse est un concept très utile, mais présente certaines réserves, notamment le fait qu'il est facile de rater le (s) moment (s) où le résultat sera nécessaire, en particulier dans les cas où le résultat est utilisé plusieurs fois.Dans le cas d'un
foreach
, le résultat n'est demandé qu'une fois. Dans le cas de afor
tel qu’implémenté dans le code écrit de manière incorrecte ci - dessus , le résultat est demandé 94 fois , soit 47 × 2:Chaque fois
cities.Count()
est appelé (47 fois),Chaque fois
cities.ElementAt(i)
est appelé (47 fois).Interroger une base de données 94 fois au lieu d'une est terrible, mais ce n'est pas la pire chose qui puisse arriver. Imaginons, par exemple, ce qui se passerait si la
select
requête était précédée d'une requête qui insère également une ligne dans la table. Effectivement, nous aurions unefor
base de données appelée 2,147,483,647 fois dans la base de données , à moins que celle-ci ne tombe en panne auparavant.Bien sûr, mon code est biaisé. J'ai délibérément utilisé la paresse de
IEnumerable
et l' ai écrit de manière à appeler à plusieurs reprisesListCities()
. On peut noter qu'un débutant ne fera jamais cela, car:Le
IEnumerable<T>
n'a pas la propriétéCount
, mais seulement la méthodeCount()
. L'appel d'une méthode est effrayant, et on peut s'attendre à ce que le résultat ne soit pas mis en cache et ne convienne pas dans unfor (; ...; )
bloc.L'indexation n'est pas disponible pour
IEnumerable<T>
et il n'est pas évident de trouver laElementAt
méthode d'extension LINQ.La plupart des débutants convertiraient probablement le résultat
ListCities()
en quelque chose qu'ils connaissent bien, comme unList<T>
.Néanmoins, ce code est très différent de l’
foreach
alternative. Encore une fois, cela donne les mêmes résultats, et cette fois, laListCities()
méthode n’est appelée qu’une fois, mais produit 575 éléments, alorsforeach
qu’elle ne donne que 47 éléments.La différence provient du fait que
ToList()
provoque toutes les données à charger à partir de la base de données. Bien queforeach
demandé seulement les villes avant "Boston", le nouveaufor
nécessite que toutes les villes soient récupérées et stockées en mémoire. Avec 575 chaînes courtes, cela ne fait probablement pas beaucoup de différence, mais que se passerait-il si nous ne récupérions que quelques lignes d'une table contenant des milliards d'enregistrements?Alors qu'est-ce que c'est
foreach
vraiment?foreach
est plus proche d'une boucle while. Le code que j'ai utilisé précédemment:peut être simplement remplacé par:
Les deux produisent le même IL. Les deux ont le même résultat. Les deux ont les mêmes effets secondaires. Bien sûr, cela
while
peut être réécrit dans un infini similairefor
, mais ce serait encore plus long et sujet aux erreurs. Vous êtes libre de choisir celui que vous trouvez plus lisible.Voulez-vous tester vous-même? Voici le code complet:
Et les résultats:
LINQ vs. manière traditionnelle
En ce qui concerne LINQ, vous voudrez peut-être apprendre la programmation fonctionnelle (FP) - et non pas avec des choses sur C # FP, mais sur un vrai langage FP comme Haskell. Les langages fonctionnels ont une manière spécifique d'exprimer et de présenter le code. Dans certaines situations, il est supérieur aux paradigmes non fonctionnels.
On sait que la PF est beaucoup plus efficace lorsqu'il s'agit de manipuler des listes ( liste en tant que terme générique, sans rapport avec
List<T>
). Compte tenu de ce fait, la possibilité d’exprimer du code C # de manière plus fonctionnelle en matière de listes est plutôt une bonne chose.Si vous n'êtes pas convaincu, comparez la lisibilité du code écrit de manière fonctionnelle et non fonctionnelle dans ma réponse précédente sur le sujet.
la source
foreach
est plus efficace quefor
, alors qu’en réalité, la disparité résulte d’un code délibérément cassé. La minutie de la réponse se rachète, mais il est facile de voir comment un observateur occasionnel pourrait tirer des conclusions erronées.Il existe déjà d'excellentes expositions sur les différences entre for et foreach. Il existe certaines fausses représentations du rôle de LINQ.
La syntaxe LINQ n'est pas simplement un sucre syntaxique donnant une approximation fonctionnelle de la programmation à C #. LINQ fournit des constructions fonctionnelles incluant tous les avantages de celles-ci pour C #. Combiné au renvoi de IEnumerable au lieu de IList, LINQ permet l'exécution différée de l'itération. Ce que les gens font généralement maintenant, c’est construire et renvoyer un IListe de leurs fonctions comme
Utilisez plutôt la syntaxe return return pour créer une énumération différée.
Maintenant, l'énumération ne se produira pas tant que vous n'aurez pas à faire la liste des tâches ni à la parcourir. Et cela ne se produit que selon les besoins (voici une énumération de Fibbonaci qui n'a pas de problème de débordement de pile)
Effectuer un foreach sur la fonction de Fibonacci retournera la séquence de 46. Si vous voulez le 30, c'est tout ce qui sera calculé
Le support dans le langage des expressions lambda (combiné aux constructions IQueryable et IQueryProvider, permet de composer de manière fonctionnelle des requêtes sur divers ensembles de données. Il incombe à IQueryProvider d’interpréter les données passées. expressions et création et exécution d'une requête à l'aide des constructions natives de la source). Je n'entrerai pas dans les détails sérieux ici, mais il y a une série de billets de blog montrant comment créer un fournisseur de requêtes SQL ici
En résumé, vous devriez préférer renvoyer IEnumerable à IList lorsque les utilisateurs de votre fonction effectueront une simple itération. Et utilisez les fonctionnalités de LINQ pour différer l'exécution de requêtes complexes jusqu'à leur utilisation.
la source
La lisibilité est dans l'oeil du spectateur. Certaines personnes pourraient dire
est parfaitement lisible; d'autres pourraient dire que c'est opaque, et préféreraient
en clarifiant ce qui se fait. Nous ne pouvons pas vous dire ce que vous trouvez plus lisible. Mais vous pourrez peut-être détecter certains de mes propres biais dans l'exemple que j'ai construit ici ...
la source
La différence entre LINQ et
foreach
se résume vraiment à deux styles de programmation différents: impératif et déclaratif.Impératif: dans ce style, vous dites à l'ordinateur "fais ceci ... maintenant fais ceci ... maintenant fais ceci maintenant fais ceci". Vous alimentez un programme une étape à la fois.
Déclaratif: dans ce style, vous indiquez à l'ordinateur ce que vous voulez que le résultat soit et laissez-le déterminer comment y parvenir.
Un exemple classique de ces deux styles compare le code d'assemblage (ou C) avec SQL. Lors du montage, vous donnez (littéralement) les instructions une par une. En SQL, vous indiquez comment associer des données et quel résultat vous souhaitez obtenir de ces données.
Un effet secondaire intéressant de la programmation déclarative est qu’elle a tendance à être un peu plus avancée. Cela permet à la plate-forme d'évoluer en dessous de vous sans que vous ayez à changer votre code. Par exemple:
Que se passe-t-il ici? Est-ce que Distinct utilise un seul noyau? Deux? Cinquante? On ne sait pas et on s'en fiche. Les développeurs .NET peuvent le réécrire à tout moment, tant qu’il continue à remplir le même objectif, notre code pourrait s’accélérer comme par magie après une mise à jour du code.
C'est le pouvoir de la programmation fonctionnelle. Et la raison pour laquelle vous trouvez ce code dans des langages comme Clojure, F # et C # (écrit avec un état d'esprit de programmation fonctionnel) est souvent 3x-10x plus petit que ses équivalents impératifs.
Enfin, j'aime bien le style déclaratif car en C #, la plupart du temps, cela me permet d'écrire du code qui ne mute pas les données. Dans l'exemple ci-dessus,
Distinct()
ne change pas de barre, il retourne une nouvelle copie des données. Cela signifie que quelle que soit la barre, quelle que soit sa provenance, elle n’a pas soudainement changé.Donc, comme le disent les autres affiches, apprenez la programmation fonctionnelle. Ça va changer ta vie. Et si vous le pouvez, faites-le dans un vrai langage de programmation fonctionnel. Je préfère Clojure, mais F # et Haskell sont également d'excellents choix.
la source
var foo = bar.Distinct()
est essentiellement unIEnumerator<T>
jusqu'à ce que vous appeliez.ToList()
ou.ToArray()
. C'est une distinction importante parce que si vous ne le savez pas, cela peut donner lieu à des bogues difficiles à comprendre.D'autres développeurs de l'équipe peuvent-ils lire LINQ?
Sinon, ne l'utilisez pas ou l'une des deux choses suivantes se produira:
Une boucle pour chaque boucle est parfaite pour parcourir une liste, mais si ce n’est pas ce que vous voulez faire, n’en utilisez pas une.
la source