L'instruction Switch est une des raisons principales pour lesquelles j'aime les constructions switch
vs. if/else if
. Un exemple est en ordre ici:
static string NumberToWords(int number)
{
string[] numbers = new string[]
{ "", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine" };
string[] tens = new string[]
{ "", "", "twenty", "thirty", "forty", "fifty",
"sixty", "seventy", "eighty", "ninety" };
string[] teens = new string[]
{ "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
"sixteen", "seventeen", "eighteen", "nineteen" };
string ans = "";
switch (number.ToString().Length)
{
case 3:
ans += string.Format("{0} hundred and ", numbers[number / 100]);
case 2:
int t = (number / 10) % 10;
if (t == 1)
{
ans += teens[number % 10];
break;
}
else if (t > 1)
ans += string.Format("{0}-", tens[t]);
case 1:
int o = number % 10;
ans += numbers[o];
break;
default:
throw new ArgumentException("number");
}
return ans;
}
Les gens intelligents sont en colère parce que les string[]
s devraient être déclarés en dehors de la fonction: eh bien, ils le sont, ce n'est qu'un exemple.
Le compilateur échoue avec l'erreur suivante:
Le contrôle ne peut pas passer d’une étiquette de cas ("cas 3:") à une autre Le contrôle ne peut pas passer d’une étiquette de cas ("cas 2:") à une autre
Pourquoi? Et est-il possible d’obtenir ce genre de comportement sans avoir trois if
s?
(Copier/coller d'un réponse fournie ailleurs )
La chute de switch
-case
s peut être obtenue en n’ayant pas de code dans un case
(voir case 0
), ou en utilisant le spécial goto case
(voir case 1
) ou goto default
(voir case 2
) formes:
switch (/*...*/) {
case 0: // shares the exact same code as case 1
case 1:
// do something
goto case 2;
case 2:
// do something else
goto default;
default:
// do something entirely different
break;
}
Le "pourquoi" consiste à éviter les retombées accidentelles, et je les en remercie. C’est une source de bogues courante en C et en Java.
La solution de contournement consiste à utiliser goto, par exemple.
switch (number.ToString().Length)
{
case 3:
ans += string.Format("{0} hundred and ", numbers[number / 100]);
goto case 2;
case 2:
// Etc
}
La conception générale de l'interrupteur/boîtier est un peu regrettable à mon avis. Cela collait trop près de C - il y a quelques changements utiles qui pourraient être apportés en termes de portée, etc. On pourrait soutenir qu'un commutateur plus intelligent qui pourrait faire la correspondance de modèle, etc. serait utile, mais cela change vraiment d'un commutateur à "vérifier une séquence de conditions" - à quel point un nom différent serait peut-être appelé.
Switch fallthrough est historiquement l’une des sources majeures de bugs dans les logiciels modernes. Le concepteur de langage a décidé de rendre obligatoire le saut à la fin du cas, à moins que vous ne passiez directement au cas suivant sans traitement.
switch(value)
{
case 1:// this is still legal
case 2:
}
Pour ajouter aux réponses ici, je pense qu’il est intéressant d’examiner la question opposée conjointement avec celle-ci, à savoir. pourquoi C a-t-il permis la chute en premier lieu?
Tout langage de programmation a bien sûr deux objectifs:
La création de tout langage de programmation est donc un équilibre entre la meilleure façon de servir ces deux objectifs. D’une part, plus il est facile de transformer en instructions informatiques (qu’il s’agisse de code machine, de bytecode comme IL ou les instructions sont interprétées lors de l’exécution), plus le processus de compilation ou d’interprétation sera efficace, plus efficace et plus fiable. compact en sortie. Poussé à l'extrême, cet objectif se traduit par une simple écriture dans Assembly, IL, voire par des codes d'opération bruts, car la compilation la plus facile consiste à ne pas en compiler du tout.
Inversement, plus le langage exprime l'intention du programmeur et non les moyens utilisés à cette fin, plus le programme est compréhensible, tant en écriture qu'en maintenance.
Maintenant, switch
aurait toujours pu être compilé en le convertissant en chaîne équivalente de blocs if-else
ou similaire, mais elle a été conçue pour permettre la compilation dans un motif Assembly particulier, où l'on prend une valeur, calcule un son décalage (que ce soit en recherchant une table indexée par un hachage parfait de la valeur ou par une arithmétique réelle sur la valeur *). Il convient de noter à ce stade qu’aujourd’hui, la compilation en C # transforme parfois switch
en équivalent if-else
, et utilise parfois une approche de saut basée sur le hachage (et également en C, C++ et d’autres langages avec des fonctions similaires). syntaxe).
Dans ce cas, il y a deux bonnes raisons d'autoriser les retombées:
Quoi qu'il en soit, cela se produit naturellement de toute façon: si vous construisez une table de saut dans un ensemble d'instructions et si l'un des lots d'instructions précédents ne contient aucune sorte de saut ou de retour, l'exécution se poursuivra naturellement dans le prochain lot. Permettre une chute directe était ce qui "se produirait" si vous transformiez la switch
- en utilisant C en table de saut, en utilisant le code machine.
Les codeurs qui écrivaient dans Assembly étaient déjà habitués à l'équivalent: lorsqu'ils écrivaient une table de sauts à la main dans Assembly, ils devaient se demander si un bloc de code donné se terminerait par un retour, un saut en dehors de la table ou continuerait simplement. au bloc suivant. En tant que tel, le fait que le codeur ajoute un break
explicite, le cas échéant, était également "naturel" pour le codeur.
À l'époque, il s'agissait donc d'une tentative raisonnable d'équilibrer les deux objectifs d'un langage informatique en ce qui concerne à la fois le code machine produit et l'expressivité du code source.
Quatre décennies plus tard, les choses ne sont pas tout à fait les mêmes, pour plusieurs raisons:
switch
soit d'être transformée en if-else
parce qu'elle a été jugée l'approche la plus efficace, ou bien d'être transformée en une variante particulièrement ésotérique de l'approche par saut de table sont plus élevées. . La correspondance entre les approches de niveau supérieur et inférieur n'est pas aussi forte qu'auparavant.switch
utilisaient une chute au-delà de plusieurs étiquettes sur le même bloc. le cas d’utilisation utilisé ici signifiait que ces 3% étaient en réalité beaucoup plus élevés que la normale). Ainsi, la langue étudiée rend l'inhabituel plus facile à satisfaire que le commun.En rapport avec ces deux derniers points, considérons la citation suivante de l'édition actuelle de K & R:
Passer d'un cas à l'autre n'est pas robuste, il est sujet à la désintégration lorsque le programme est modifié. À l'exception de plusieurs étiquettes pour un même calcul, les retombées doivent être utilisées avec parcimonie et commentées.
En règle générale, mettez une pause après le dernier cas (le cas par défaut ici), même si c'est logiquement inutile. Un jour, lorsqu'un autre cas sera ajouté à la fin, cette programmation défensive vous épargnera.
Donc, de la bouche du cheval, la chute en C est problématique. Il est considéré comme une bonne pratique de toujours documenter les retombées avec des commentaires, ce qui est une application du principe général selon lequel il convient de documenter où l'on fait quelque chose d'inhabituel, car c'est ce qui déclenchera un examen ultérieur du code et/ou lui donnera l'aspect du code. a un bogue de novice en elle quand elle est en fait correcte.
Et quand vous y réfléchissez, codez comme ceci:
switch(x)
{
case 1:
foo();
/* FALLTHRU */
case 2:
bar();
break;
}
Is ajoutant quelque chose pour rendre la chute directe explicite dans le code, ce n'est tout simplement pas quelque chose qui peut être détecté (ou dont l'absence peut être détectée) par le compilateur.
En tant que tel, le fait qu’on doive être explicite avec une chute directe en C # n’ajoute aucune pénalité aux personnes qui écrivent bien dans d’autres langages de style C, car elles seraient déjà explicites lors de leurs retombées. †
Enfin, l'utilisation de goto
est déjà une norme de C et d'autres langages similaires:
switch(x)
{
case 0:
case 1:
case 2:
foo();
goto below_six;
case 3:
bar();
goto below_six;
case 4:
baz();
/* FALLTHRU */
case 5:
below_six:
qux();
break;
default:
quux();
}
Dans ce type de cas où nous souhaitons inclure un bloc dans le code exécuté pour une valeur autre que celle qui en amène un au bloc précédent, nous devons déjà utiliser goto
. (Bien sûr, il existe des moyens et des moyens d'éviter cela avec différentes conditions, mais c'est vrai pour à peu près tout ce qui concerne cette question). En tant que tel, C # a construit sur la manière déjà normale de traiter une situation dans laquelle nous souhaitons utiliser plus d'un bloc de code dans un switch
, et l'a simplement généralisé pour couvrir également les retombées. Cela a également rendu les deux cas plus pratiques et auto-documentés, car nous devons ajouter une nouvelle étiquette en C mais nous pouvons utiliser le case
comme étiquette en C #. En C #, nous pouvons supprimer le libellé below_six
et utiliser goto case 5
, qui est plus clair quant à ce que nous faisons. (Nous devrons également ajouter break
pour le default
, ce que j'ai omis juste pour rendre le code C ci-dessus clairement pas le code C #).
En résumé donc:
break
implicite, pour faciliter l'apprentissage du langage par ceux qui sont familiers avec des langages similaires et faciliter le portage.goto
pour frapper le même bloc à partir de différentes étiquettes case
comme celle utilisée en C. Il la généralise simplement à d'autres cas.goto
plus pratique et plus claire qu'en C, en permettant aux instructions case
d'agir en tant qu'étiquettes.Dans l'ensemble, une décision de conception assez raisonnable
* Certaines formes de BASIC permettraient de faire des choses comme GOTO (x AND 7) * 50 + 240
qui, bien qu’elle soit fragile et par conséquent un argument particulièrement convaincant en faveur de l’interdiction de goto
, sert à montrer un équivalent en langage supérieur du type de Le code de niveau inférieur peut effectuer un saut basé sur l'arithmétique d'une valeur, ce qui est beaucoup plus raisonnable lorsqu'il s'agit d'une compilation plutôt que d'un élément à gérer manuellement. Les implémentations de Duff's Device, en particulier, se prêtent bien au code machine équivalent ou IL, car chaque bloc d'instructions aura souvent la même longueur sans nécessiter l'ajout de remplisseurs nop
.
† Duff's Device revient ici à nouveau, à titre d'exception raisonnable. Le fait que, avec ce schéma et des schémas similaires, il y ait une répétition des opérations sert à rendre l'utilisation des retombées relativement claire, même sans commentaire explicite à cet effet.
Vous pouvez 'goto case label' http://www.blackwasp.co.uk/CSharpGoto.aspx
L'instruction goto est une commande simple qui transfère inconditionnellement le contrôle du programme à une autre instruction. La commande est souvent critiquée par certains développeurs qui préconisent sa suppression de tous les langages de programmation de haut niveau car elle peut entraîner code spaghetti . Cela se produit lorsqu'il y a tellement d'instructions goto ou d'invitations similaires que le code devient difficile à lire et à maintenir. Cependant, certains programmeurs soulignent que la déclaration goto, utilisée avec précaution, fournit une solution élégante à certains problèmes ...
Ils ont omis ce comportement de par leur conception pour éviter les problèmes lorsqu'ils étaient utilisés sans testament.
Il ne peut être utilisé que s'il n'y a pas d'énoncé dans la partie affaire, comme:
switch (whatever)
{
case 1:
case 2:
case 3: boo; break;
}
Ils ont modifié le comportement de l'instruction switch (à partir de C/Java/C++) pour c #. Je suppose que le raisonnement était que les gens ont oublié la chute et que des erreurs ont été commises. Un livre que j'ai lu m'a dit d'utiliser la méthode goto pour simuler, mais cela ne me semble pas une bonne solution.
Après chaque déclaration de cas, une instruction pause ou goto est requise même s'il s'agit d'un cas par défaut.
Vous pouvez obtenir des résultats similaires à c ++ à l'aide du mot clé goto.
EX:
switch(num)
{
case 1:
goto case 3;
case 2:
goto case 3;
case 3:
//do something
break;
case 4:
//do something else
break;
case default:
break;
}
Juste une petite note pour ajouter que le compilateur de Xamarin s’est réellement trompé et qu’il permettait de s’écouler. Il a soi-disant été corrigé, mais n'a pas été publié. J'ai découvert cela dans un code qui était en train de tomber et le compilateur ne s'est pas plaint.
le commutateur (référence C #) dit
C # nécessite la fin des sections d’interrupteur, y compris la dernière,
Donc, vous devez également ajouter un break;
à votre section default
, sinon il y aura toujours une erreur de compilation.
Une instruction de saut telle qu'une pause est requise après chaque bloc de casse, y compris le dernier bloc, qu'il s'agisse d'une déclaration de casse ou d'une déclaration par défaut. À une exception près (contrairement à l'instruction de commutateur C++), C # ne prend pas en charge le passage implicite d'une étiquette de cas à une autre. La seule exception est si une instruction case n'a pas de code.