J'ai une switch
structure qui a plusieurs cas à gérer. Le switch
opère sur un enum
qui pose le problème de la duplication de code via des valeurs combinées:
// All possible combinations of One - Eight.
public enum ExampleEnum {
One,
Two, TwoOne,
Three, ThreeOne, ThreeTwo, ThreeOneTwo,
Four, FourOne, FourTwo, FourThree, FourOneTwo, FourOneThree,
FourTwoThree, FourOneTwoThree
// ETC.
}
Actuellement, la switch
structure traite chaque valeur séparément:
// All possible combinations of One - Eight.
switch (enumValue) {
case One: DrawOne; break;
case Two: DrawTwo; break;
case TwoOne:
DrawOne;
DrawTwo;
break;
case Three: DrawThree; break;
...
}
Vous avez l'idée là. J'ai actuellement cette if
structure en pile pour gérer les combinaisons avec une seule ligne:
// All possible combinations of One - Eight.
if (One || TwoOne || ThreeOne || ThreeOneTwo)
DrawOne;
if (Two || TwoOne || ThreeTwo || ThreeOneTwo)
DrawTwo;
if (Three || ThreeOne || ThreeTwo || ThreeOneTwo)
DrawThree;
Cela pose le problème des évaluations logiques incroyablement longues, qui déroutent à la lecture et sont difficiles à maintenir. Après avoir réexaminé cette réflexion, j'ai commencé à réfléchir à des alternatives et à l'idée d'une switch
structure avec des retombées entre les cas.
Je dois utiliser un goto
dans ce cas car C#
ne permet pas de tomber à travers. Cependant, cela évite les chaînes logiques incroyablement longues, même s'il saute dans la switch
structure, tout en permettant la duplication de code.
switch (enumVal) {
case ThreeOneTwo: DrawThree; goto case TwoOne;
case ThreeTwo: DrawThree; goto case Two;
case ThreeOne: DrawThree; goto default;
case TwoOne: DrawTwo; goto default;
case Two: DrawTwo; break;
default: DrawOne; break;
}
Ce n'est toujours pas une solution suffisamment propre et le goto
mot clé que je voudrais éviter est associé à une honte . Je suis sûr qu'il doit y avoir un meilleur moyen de nettoyer cela.
Ma question
Existe-t-il une meilleure façon de gérer ce cas spécifique sans affecter la lisibilité et la maintenabilité?
la source
goto
lorsque la structure de haut niveau n'existe pas dans votre langue. Je souhaite parfois qu'il y ait unfallthru
; mot-clé pour se débarrasser de cette utilisation particulière degoto
mais bon.goto
langage de haut niveau tel que C #, vous avez probablement négligé de nombreuses autres (et meilleures) alternatives de conception et / ou de mise en œuvre. Très découragé.Réponses:
Je trouve le code difficile à lire avec les
goto
déclarations. Je recommanderais de structurer votreenum
différemment. Par exemple, si votreenum
était un champ de bits où chaque bit représentait l'un des choix, cela pourrait ressembler à ceci:L'attribut Flags indique au compilateur que vous définissez des valeurs qui ne se chevauchent pas. Le code qui appelle ce code peut définir le bit approprié. Vous pouvez ensuite faire quelque chose comme ceci pour clarifier ce qui se passe:
Cela nécessite le code qui définit
myEnum
pour définir les champs de bits correctement et marqué avec l'attribut Flags. Mais vous pouvez le faire en modifiant les valeurs des enums de votre exemple pour:Lorsque vous écrivez un nombre dans le formulaire
0bxxxx
, vous le spécifiez en binaire. Vous pouvez donc voir que nous avons défini les bits 1, 2 ou 3 (bien, techniquement, 0, 1 ou 2, mais vous avez l’idée). Vous pouvez également nommer des combinaisons en utilisant un bit OU si les combinaisons peuvent être fréquemment définies ensemble.la source
public enum ExampleEnum { One = 1 << 0, Two = 1 << 1, Three = 1 << 2, OneAndTwo = One | Two, OneAndThree = One | Three, TwoAndThree = Two | Three };
. Pas besoin d'insister sur C # 7 +.[Flags]
attribut ne signale rien au compilateur. C’est la raison pour laquelle vous devez toujours déclarer explicitement les valeurs d’énum en tant que puissances de 2.HasFlag
est un terme descriptif et explicite pour l’opération en cours et extrait l’implémentation de la fonctionnalité. Utiliser&
parce qu'il est commun à d'autres langages n'a pas plus de sens que d'utiliser unbyte
type au lieu de déclarer unenum
.OMI la racine du problème est que ce morceau de code ne devrait même pas exister.
Vous avez apparemment trois conditions indépendantes et trois actions indépendantes à prendre si ces conditions sont vraies. Alors, pourquoi tout cela est-il canalisé dans un code qui a besoin de trois drapeaux booléens pour lui dire quoi faire (que vous les masquiez ou non en une énumération) et ensuite une combinaison de trois choses indépendantes? Le principe de la responsabilité unique semble avoir une journée de congé ici.
Placez les appels sur les trois fonctions auxquelles vous appartenez (c’est-à-dire où vous découvrez la nécessité de faire les actions) et consignez le code de ces exemples dans la corbeille.
S'il y avait dix drapeaux et trois actions, étendriez-vous ce type de code pour traiter 1024 combinaisons différentes? J'espère que non! Si 1024 est trop, 8 est aussi trop, pour la même raison.
la source
Ne jamais utiliser gotos est l’un des concepts de «l’informatique » qui «ment aux enfants» . C'est le bon conseil 99% du temps, et les moments où il n'en est pas une sont tellement rares et spécialisés que c'est bien mieux pour tout le monde s'il est simplement expliqué aux nouveaux codeurs comme "ne les utilisez pas".
Alors, quand devraient- ils être utilisés? Il existe quelques scénarios , mais celui que vous semblez toucher est le suivant: lorsque vous codez une machine à états . S'il n'y a pas meilleure expression mieux organisée et structurée de votre algorithme qu'une machine à états, son expression naturelle dans le code implique des branches non structurées, et il n'y a pas grand chose à faire pour ce qui ne fait pas la structure du machine à états elle-même plus obscure, plutôt que moins.
Les rédacteurs de compilateurs le savent bien, c'est pourquoi le code source de la plupart des compilateurs qui implémentent des analyseurs LALR * contient des gotos. Cependant, très peu de gens coderont réellement leurs propres analyseurs et analyseurs syntaxiques lexicaux.
* - IIRC, il est possible de mettre en œuvre des grammaires LALL de descente entièrement récursive sans recourir à des tables de saut ou à d’autres instructions de contrôle non structurées. Par conséquent, si vous êtes vraiment anti-goto, c’est une solution.
Maintenant, la question suivante est: "Cet exemple est-il l'un de ces cas?"
Ce que je vois, c'est que vous avez trois états différents possibles en fonction du traitement de l'état actuel. Puisque l’un d’eux ("default") n’est qu’une ligne de code, techniquement, vous pouvez vous en débarrasser en pointant cette ligne de code à la fin des états auxquels elle s’applique. Cela vous mènerait à 2 états possibles.
L'un des autres ("Trois") n'est branché qu'à un endroit que je peux voir. Donc, vous pouvez vous en débarrasser de la même manière. Vous vous retrouveriez avec un code qui ressemble à ceci:
Cependant, encore une fois, c’était un exemple de jouet que vous avez fourni. Dans les cas où "par défaut" contient une quantité de code non négligeable, "trois" passe de plusieurs états à une transition, ou (plus important encore ) une maintenance supplémentaire risque d'ajouter ou de compliquer les états , vous seriez honnêtement mieux. utiliser gotos (et peut-être même se débarrasser de la structure enumère qui cache la nature de la machine à états, à moins qu'il y ait une bonne raison pour qu'elle reste).
la source
goto
.La meilleure réponse est d' utiliser le polymorphisme .
Une autre réponse, qui, IMO, rend les choses si plus claires et peut-être plus courtes :
goto
est probablement mon 58ème choix ici ...la source
Pourquoi pas ceci:
OK, je suis d’accord, c’est du hack (je ne suis pas un développeur C #, excusez-moi donc pour le code), mais du point de vue de l’efficacité, c’est un must? L'utilisation d'énums en tant qu'index de tableau est valide C #.
la source
int[ExempleEnum.length] COUNTS = { 1, 3, 4, 2, 5, 3 };
:?Si vous ne pouvez pas ou ne voulez pas utiliser les indicateurs, utilisez une fonction récursive. En mode de publication 64 bits, le compilateur produira un code très similaire à votre
goto
déclaration. Vous n'avez simplement pas à vous en occuper.la source
La solution acceptée est correcte et constitue une solution concrète à votre problème. Cependant, je voudrais poser une solution alternative, plus abstraite.
D'après mon expérience, l'utilisation d'énums pour définir le flux de la logique constitue une odeur de code, car elle est souvent le signe d'une conception de classe médiocre.
J'ai rencontré un exemple concret de ce qui se passe dans le code sur lequel j'ai travaillé l'année dernière. Le développeur d'origine avait créé une classe unique, chargée de la logique d'importation et d'exportation, et basculant entre les deux sur la base d'une énumération. À présent, le code était similaire et comportait un code dupliqué, mais il était suffisamment différent pour rendre le code beaucoup plus difficile à lire et pratiquement impossible à tester. J'ai fini par refactoriser cela en deux classes distinctes, ce qui les a simplifiées et m'a permis de repérer et d'éliminer un certain nombre de bogues non signalés.
Encore une fois, je dois dire que l'utilisation d'énums pour contrôler le flux de la logique est souvent un problème de conception. Dans le cas général, Enums devrait être principalement utilisé pour fournir des valeurs adaptées au type et sécurisées pour le consommateur, lorsque les valeurs possibles sont clairement définies. Ils sont mieux utilisés en tant que propriété (par exemple, en tant qu'ID de colonne dans une table) qu'en tant que mécanisme de contrôle logique.
Considérons le problème présenté dans la question. Je ne connais pas vraiment le contexte ici, ni ce que représente cette énumération. Est-ce que c'est dessiner des cartes? Dessiner des images? Tirer du sang? L'ordre est-il important? Je ne sais pas non plus à quel point la performance est importante. Si les performances ou la mémoire sont essentielles, cette solution ne sera probablement pas celle que vous souhaitez.
Dans tous les cas, considérons l'énumération:
Nous avons ici un certain nombre de valeurs enum différentes représentant différents concepts d’entreprise.
Ce que nous pourrions utiliser à la place, ce sont des abstractions pour simplifier les choses.
Considérons l'interface suivante:
Nous pouvons alors implémenter cela en tant que classe abstraite:
Nous pouvons avoir une classe concrète pour représenter les dessins un, deux et trois (qui, par souci d'argument, ont une logique différente). Ceux-ci pourraient potentiellement utiliser la classe de base définie ci-dessus, mais je suppose que le concept DrawOne est différent du concept représenté par l'énumération:
Et maintenant, nous avons trois classes distinctes qui peuvent être composées pour fournir la logique aux autres classes.
Cette approche est beaucoup plus verbeuse. Mais cela a des avantages.
Considérez la classe suivante, qui contient un bogue:
À quel point est-il plus simple de repérer l'appel drawThree.Draw () manquant? Et si l'ordre est important, l'ordre des appels de tirage au sort est également très facile à voir et à suivre.
Inconvénients de cette approche:
Avantages de cette approche:
Considérez cette approche (ou une approche similaire) chaque fois que vous ressentez le besoin de rédiger un code de contrôle logique complexe dans les instructions de cas. Future, vous serez heureux de l'avoir fait.
la source
Si vous souhaitez utiliser un commutateur ici, votre code sera réellement plus rapide si vous traitez chaque cas séparément.
une seule opération arithmétique est effectuée dans chaque cas
également, comme d’autres l’ont déjà dit, si vous envisagez d’utiliser gotos, vous devriez probablement repenser votre algorithme (bien que je concède que le manque de cas de C # peut être une raison pour utiliser un goto). Voir le célèbre article d'Edgar Dijkstra "Aller à la déclaration considérée comme nuisible"
la source
Pour votre exemple particulier, puisque tout ce que vous vouliez vraiment de l'énumération était un indicateur "faire / ne pas-ne" pour chacune des étapes, la solution qui réécrit vos trois
if
déclarations est préférable à unswitch
et il est bon que vous en fassiez la réponse acceptée. .Mais si vous aviez une logique plus complexe qui ne pas fonctionner de manière proprement, je trouve encore le
goto
s dans laswitch
déclaration confusion. Je préférerais voir quelque chose comme ça:Ce n'est pas parfait, mais je pense que c'est mieux ainsi qu'avec le
goto
s. Si les séquences d'événements sont si longues et se dupliquent tellement qu'il n'a pas vraiment de sens d'épeler la séquence complète pour chaque cas, je préférerais un sous-programme plutôt quegoto
pour réduire la duplication de code.la source
La raison pour détester le
goto
mot clé est un code commeOops! Cela ne va clairement pas au travail. La
message
variable n'est pas définie dans ce chemin de code. Donc C # ne passera pas ça. Mais cela pourrait être implicite. ConsidérerEt supposons qu’il
display
y ait ensuite laWriteLine
déclaration.Ce type de bogue peut être difficile à trouver car
goto
obscurcit le chemin du code.Ceci est un exemple simplifié. Supposons qu'un exemple réel ne soit pas aussi évident. Il peut y avoir cinquante lignes de code entre
label:
et l’utilisation demessage
.Le langage peut aider à résoudre ce problème en limitant l'utilisation qui
goto
peut être faite, en descendant seulement des blocs. Mais le C #goto
n'est pas limité comme ça. Il peut sauter par-dessus le code. De plus, si vous voulez limitergoto
, il est également bon de changer le nom. D'autres langues sont utiliséesbreak
pour descendre des blocs, avec un nombre (de blocs à quitter) ou une étiquette (sur l'un des blocs).Le concept de
goto
est une instruction de bas niveau en langage machine. Mais la raison principale pour laquelle nous avons des langages de niveau supérieur est que nous sommes limités aux abstractions de niveau supérieur, par exemple la portée variable.Cela étant dit, si vous utilisez le C #
goto
dans uneswitch
instruction pour passer d'un cas à l'autre, c'est raisonnablement inoffensif. Chaque cas est déjà un point d'entrée. Je pense toujours que le qualifier degoto
stupide dans cette situation, car il confond cet usage inoffensif degoto
avec des formes plus dangereuses. Je préférerais de beaucoup qu'ils utilisent quelque chose commecontinue
ça pour ça. Mais pour une raison quelconque, ils ont oublié de me demander avant d’écrire la langue.la source
C#
langue, vous ne pouvez pas sauter par-dessus les déclarations de variables. Pour preuve, lancez une application console et entrez le code que vous avez fourni dans votre message. Vous obtiendrez une erreur de compilateur dans votre étiquette indiquant que l'erreur est l' utilisation de la variable locale non affectée 'message' . Dans les langues où cela est autorisé, c'est une préoccupation valable, mais pas dans le langage C #.Lorsque vous avez autant de choix (et même plus, comme vous dites), alors peut-être que ce n'est pas du code mais des données.
Créez un dictionnaire mappant des valeurs d'énumération en actions, exprimé sous forme de fonctions ou sous la forme d'un type d'énum plus simple représentant les actions. Ensuite, votre code peut être réduit à une simple recherche dans un dictionnaire, suivi de l’appel de la valeur de la fonction ou de l’activation des choix simplifiés.
la source
Utilisez une boucle et la différence entre pause et continuer.
la source