Lorsque vous devez répéter un lecteur dont le nombre d'éléments à lire est inconnu, et la seule façon de le faire est de continuer à lire jusqu'à la fin.
C'est souvent l'endroit où vous avez besoin d'une boucle sans fin.
Il y a toujours
true
qui indique qu'il doit y avoir une instructionbreak
ou quelque part à l'intérieur du bloc.return
int offset = 0; while(true) { Record r = Read(offset); if(r == null) { break; } // do work offset++; }
Il y a la méthode de la double lecture pour la boucle.
Record r = Read(0); for(int offset = 0; r != null; offset++) { r = Read(offset); if(r != null) { // do work } }
Il y a la boucle de lecture unique. Toutes les langues ne prennent pas en charge cette méthode .
int offset = 0; Record r = null; while((r = Read(++offset)) != null) { // do work }
Je me demande quelle approche est la moins susceptible d'introduire un bogue, la plus lisible et la plus utilisée.
Chaque fois que je dois en écrire un, je pense "qu'il doit y avoir une meilleure façon" .
c#
code-quality
readability
Reactgular
la source
la source
Réponses:
Je ferais un pas en arrière ici. Vous vous concentrez sur les détails pointilleux du code mais manquez l'image plus grande. Jetons un coup d'œil à l'une de vos boucles d'exemple:
Quelle est la signification de ce code? La signification est "faire un travail sur chaque enregistrement d'un fichier". Mais ce n'est pas à cela que ressemble le code . Le code ressemble à "maintenir un décalage. Ouvrez un fichier. Entrez une boucle sans condition de fin. Lisez un enregistrement. Testez la nullité." Tout ça avant d'arriver au travail! La question que vous devriez poser est " comment puis-je faire en sorte que l'apparence de ce code corresponde à sa sémantique? " Ce code devrait être:
Maintenant, le code se lit comme son intention. Séparez vos mécanismes de votre sémantique . Dans votre code d'origine, vous mélangez le mécanisme - les détails de la boucle - avec la sémantique - le travail effectué pour chaque enregistrement.
Maintenant, nous devons mettre en œuvre
RecordsFromFile()
. Quelle est la meilleure façon de mettre cela en œuvre? On s'en fout? Ce n'est pas le code que quiconque va consulter. C'est le code du mécanisme de base et ses dix lignes. Écrivez-le comme vous le souhaitez. Que dis-tu de ça?Maintenant que nous manipulons une séquence d'enregistrements calculée paresseusement, toutes sortes de scénarios deviennent possibles:
Etc.
Chaque fois que vous écrivez une boucle, demandez-vous "cette boucle se lit-elle comme un mécanisme ou comme le sens du code?" Si la réponse est "comme un mécanisme", essayez de déplacer ce mécanisme dans sa propre méthode et écrivez le code pour rendre la signification plus visible.
la source
Vous n'avez pas besoin d'une boucle sans fin. Vous ne devriez jamais en avoir besoin dans les scénarios de lecture C #. C'est mon approche préférée, en supposant que vous avez vraiment besoin de maintenir un décalage:
Cette approche reconnaît le fait qu'il existe une étape de configuration pour le lecteur, il y a donc deux appels de méthode de lecture. La
while
condition est en haut de la boucle, juste au cas où il n'y aurait aucune donnée dans le lecteur.la source
Eh bien, cela dépend de votre situation. Mais l'une des solutions les plus "C # -ish" à laquelle je peux penser est d'utiliser l'interface IEnumerable intégrée et une boucle foreach. L'interface pour IEnumerator appelle uniquement MoveNext avec true ou false, donc la taille peut être inconnue. Ensuite, votre logique de terminaison est écrite une fois - dans l'énumérateur - et vous n'avez pas à répéter à plusieurs endroits.
MSDN fournit un exemple d'IEnumerator <T> . Vous devrez également créer un IEnumerable <T> pour renvoyer l'IEnumerator <T>.
la source
Quand j'ai des opérations d'initialisation, de condition et d'incrémentation, j'aime utiliser les boucles for de langages comme C, C ++ et C #. Comme ça:
Ou ceci si vous pensez que c'est plus lisible. Personnellement, je préfère le premier.
la source