Existe-t-il une différence significative entre l'utilisation de if / else et la casse de commutateur en C #?

219

Quel est l'avantage / inconvénient de l'utilisation d'une switchdéclaration par rapport à un if/elseen C #. Je ne peux pas imaginer qu'il y ait une grande différence, à part peut-être l'apparence de votre code.

Y a-t-il une raison pour laquelle l'IL résultant ou les performances d'exécution associées seraient radicalement différentes?

En relation: Qu'est-ce qui est plus rapide, activer la chaîne ou le type elseif?

Matthew M. Osborn
la source
3
Cette question n'est intéressante en théorie que pour la majorité des développeurs, sauf si vous vous trouvez souvent à répéter UN MILLIARD de fois. (Ensuite, utilisez une instruction switch et passez de 48 à 43 secondes ...) Ou selon les mots de Donald Knuth: "Il faut oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal" en.wikipedia.org/wiki/Program_optimization#When_to_optimize
Père
J'utilise souvent if / else au lieu de switch en raison de la portée partagée changeante de switch.
Josh

Réponses:

341

L'instruction SWITCH produit uniquement le même assembly que les IF en mode débogage ou compatibilité. Dans la version, il sera compilé dans la table de saut (via l'instruction 'switch' MSIL) - qui est O (1).

C # (contrairement à de nombreux autres langages) permet également d'activer les constantes de chaîne - et cela fonctionne un peu différemment. Il n'est évidemment pas pratique de construire des tables de saut pour des chaînes de longueurs arbitraires, donc le plus souvent un tel commutateur sera compilé en pile de FI.

Mais si le nombre de conditions est suffisamment grand pour couvrir les frais généraux, le compilateur C # créera un objet HashTable, le remplira avec des constantes de chaîne et fera une recherche sur cette table suivie d'un saut. La recherche de table de hachage n'est pas strictement O (1) et a des coûts constants notables, mais si le nombre d'étiquettes de cas est grand, il sera beaucoup plus rapide que de comparer à chaque constante de chaîne dans les FI.

Pour résumer, si le nombre de conditions est supérieur à 5 ou plus, préférez SWITCH à IF, sinon utilisez ce qui semble mieux.

ima
la source
1
Êtes-vous sûr que le compilateur C # produira une table de hachage? Le point que j'ai fait valoir sur la table de hachage dans notre commentaire ci-dessus concernait les compilateurs natifs, pas le compilateur C #. Quel seuil le compilateur C # utilise-t-il pour produire une table de hachage?
Scott Wisniewski
8
Une dizaine je pense. 20 pour être du bon côté. Et btw, ma colère n'est pas vous, mais sur les gens qui votent et acceptent.
ima
48
Un peu d'expérimentation suggère de compter <= 6: "si"; count> = 7: dictionnaire. C'est avec le compilateur MS .NET 3.5 C # - cela pourrait changer entre les versions et les fournisseurs, bien sûr.
Jon Skeet
37
Hé, je te dois des excuses. Désolé d'être une tête d'os.
Scott Wisniewski
À titre de suivi, pour les applications pratiques, y a-t-il la plupart du temps des différences réelles? Je trouve que les instructions switch en C # sont bizarres, leur syntaxe ne ressemble à rien d'autre et je trouve qu'elles rendent mon code moins lisible, vaut-il la peine d'utiliser les instructions switch, ou devrais-je simplement programmer avec d'autres ifs et ne venir que arrière et les remplacer si je rencontre des goulots d'étranglement de performances?
Jason Masters
54

En général (compte tenu de tous les langages et de tous les compilateurs), une instruction switch PEUT PARFOIS être plus efficace qu'une instruction if / else, car il est facile pour un compilateur de générer des tables de saut à partir d'instructions switch. Il est possible de faire la même chose pour les instructions if / else, étant donné les contraintes appropriées, mais c'est beaucoup plus difficile.

Dans le cas de C #, cela est également vrai, mais pour d'autres raisons.

Avec un grand nombre de chaînes, l'utilisation d'une instruction switch présente un avantage significatif en termes de performances, car le compilateur utilise une table de hachage pour implémenter le saut.

Avec un petit nombre de cordes, les performances entre les deux sont les mêmes.

En effet, dans ce cas, le compilateur C # ne génère pas de table de saut. Au lieu de cela, il génère MSIL qui est équivalent aux blocs IF / ELSE.

Il existe une instruction MSIL "instruction de commutation" qui, lorsqu'elle est jitted, utilisera une table de saut pour implémenter une instruction de commutation. Cependant, cela ne fonctionne qu'avec des types entiers (cette question concerne les chaînes).

Pour un petit nombre de chaînes, il est plus efficace pour le compilateur de générer des blocs IF / ELSE que d'utiliser une table de hachage.

Lorsque j'ai remarqué cela à l'origine, j'ai fait l'hypothèse que, comme les blocs IF / ELSE étaient utilisés avec un petit nombre de chaînes, le compilateur faisait la même transformation pour un grand nombre de chaînes.

C'était faux. 'IMA' a eu la gentillesse de me le signaler (enfin ... il n'était pas gentil à ce sujet, mais il avait raison, et j'avais tort, ce qui est important)

J'ai également fait une supposition à propos de l'absence d'une instruction "switch" dans MSIL (je me suis dit, s'il y avait une primitive switch, pourquoi ne l'utilisaient-ils pas avec une table de hachage, donc il ne devait pas y avoir de primitive switch. ...). C'était à la fois faux et incroyablement stupide de ma part. Encore une fois, «IMA» me l'a fait remarquer.

J'ai fait les mises à jour ici parce que c'est le post le mieux noté et la réponse acceptée.

Cependant, je l'ai fait Community Wiki parce que je pense que je ne mérite pas le REP pour avoir tort. Si vous en avez l'occasion, veuillez voter pour le poste de 'ima'.

Scott Wisniewski
la source
3
Il existe une primitive de commutation dans MSIL et les instructions c # se compilent en une recherche généralement de type C. Dans certaines circonstances (plate-forme cible, commutateurs cl, etc.), le commutateur peut être étendu aux IF pendant la compilation, mais ce n'est qu'une mesure de compatibilité de secours.
ima
6
Tout ce que je peux faire, c'est m'excuser d'avoir commis une erreur stupide. Croyez-moi, je me sens stupide. Sérieusement, je pense que c'est toujours la meilleure réponse. Il est possible, dans les compilateurs natifs, d'utiliser une table de hachage pour implémenter un saut, donc ce n'est pas une chose horriblement mauvaise. J'ai fait une erreur.
Scott Wisniewski
9
ima, s'il y a des erreurs, signalez-les. On dirait que Scott se fera un plaisir de corriger le message. Sinon, les autres qui ont atteint la capacité de corriger une réponse le feront. C'est la seule façon dont un site comme celui-ci fonctionnera, et il semble, en général, que cela fonctionne. Ou prenez votre balle et rentrez chez vous :)
jwalkerjr
2
@Scott: Je vous encourage à modifier les deuxième et troisième paragraphes pour indiquer explicitement "pour les chaînes". Les gens risquent de ne pas lire la mise à jour en bas.
Jon Skeet
4
@ima Si vous pensez qu'une réponse est objectivement fausse, modifiez-la pour qu'elle soit correcte. C'est pourquoi tout le monde peut modifier les réponses.
Miles Rout
18

Trois raisons de préférer switch:

  • Un compilateur ciblant le code natif peut souvent compiler une instruction switch dans une branche conditionnelle plus un saut indirect alors qu'une séquence deif s nécessite une séquence de branches conditionnelles . En fonction de la densité des cas, de nombreux articles savants ont été rédigés sur la manière de compiler efficacement les déclarations de cas; certains sont liés depuis la page du compilateur lcc . (Lcc avait l'un des compilateurs les plus innovants pour les commutateurs.)

  • Une instruction switch est un choix parmi des alternatives mutuellement exclusives et la syntaxe de switch rend ce flux de contrôle plus transparent pour le programmeur, puis un nid d'instructions if-then-else.

  • Dans certaines langues, notamment ML et Haskell, le compilateur vérifie si vous avez omis des cas . Je considère cette fonctionnalité comme l'un des principaux avantages de ML et Haskell. Je ne sais pas si C # peut le faire.

Une anecdote: lors d'une conférence qu'il a donnée sur la réception d'un prix pour l'ensemble de sa carrière, j'ai entendu Tony Hoare dire que de toutes les choses qu'il a faites dans sa carrière, il y en avait trois dont il était le plus fier:

  • Inventer Quicksort
  • Inventer la déclaration switch (que Tony a appelée la casedéclaration)
  • Commencer et terminer sa carrière dans l'industrie

Je ne peux pas imaginer vivre sansswitch .

Norman Ramsey
la source
16

Le compilateur va optimiser à peu près tout dans le même code avec des différences mineures (Knuth, n'importe qui?).

La différence est qu'une instruction switch est plus propre que quinze si les instructions else sont liées ensemble.

Les amis ne laissent pas les amis empiler les instructions if-else.


la source
13
"Les amis ne laissent pas les amis empiler les déclarations if-else." Vous devriez faire un sitcker bumber :)
Matthew M. Osborn
14

En fait, une instruction switch est plus efficace. Le compilateur l'optimisera dans une table de recherche où avec les instructions if / else il ne peut pas. L'inconvénient est qu'une instruction switch ne peut pas être utilisée avec des valeurs variables.
Vous ne pouvez pas faire:

switch(variable)
{
   case someVariable
   break;
   default:
   break;
}

ça doit être

switch(variable)
{
  case CONSTANT_VALUE;
  break;
  default:
  break;
}
kemiller2002
la source
1
avez-vous des numéros? Je suis curieux de savoir dans quelle mesure un compilateur peut optimiser une instruction swtich sur un If / Else
Matthew M. Osborn
oui, je crois qu'une instruction switch est toujours optimisée en O (1) où une instruction if else sera O (n) où n est la position de la valeur correcte dans les instructions if / else if.
kemiller2002
Dans le cas de C #, ce n'est pas vrai, consultez mon article ci-dessous pour plus d'informations.
Scott Wisniewski
Je ne suis pas totalement certain de cela, mais je ne trouve pas d'informations dans le livre dans lequel je jure l'avoir trouvé. Êtes-vous sûr que vous ne regardez pas le code MSIL sans optimisation. Il ne créera pas la table de saut sauf si vous compilez avec l'optimisation activée.
kemiller2002
J'ai compilé à la fois en mode débogage et en mode détail, et dans les deux cas, il génère des blocs if / else. Êtes-vous sûr que le livre que vous regardiez parlait de C #? Le livre était probablement soit un livre de compilation, soit un livre sur le C ou le C ++
Scott Wisniewski
14

Je n'ai vu personne d'autre soulever le point (évident?) Que l'avantage d'efficacité supposé de l'instruction switch dépend du fait que les différents cas sont à peu près également probables. Dans les cas où l'une (ou quelques-unes) des valeurs sont beaucoup plus probables, l'échelle if-then-else peut être beaucoup plus rapide, en s'assurant que les cas les plus courants sont d'abord vérifiés:

Ainsi, par exemple:

if (x==0) then {
  // do one thing
} else if (x==1) {
  // do the other thing
} else if (x==2) {
  // do the third thing
}

contre

switch(x) {
  case 0: 
         // do one thing
         break;
  case 1: 
         // do the other thing
         break;
  case 2: 
         // do the third thing
         break;
}

Si x vaut zéro dans 90% des cas, le code "if-else" peut être deux fois plus rapide que le code basé sur le commutateur. Même si le compilateur transforme le «commutateur» en une sorte de goto intelligent piloté par table, il ne sera toujours pas aussi rapide que de simplement vérifier zéro.

Mark Bessey
la source
3
Pas d'optimisation prématurée! En général, si vous avez plus que quelques cas et qu'ils sont switchcompatibles, l' switchinstruction est meilleure (plus lisible, parfois plus rapide). Si vous savez qu'un cas est beaucoup plus probable, vous pouvez le retirer pour former une construction if- else- switchet s'il est sensiblement plus rapide , laissez- le dedans . (Répétez, si nécessaire.) IMO qui est encore raisonnablement lisible. Si le switchdégénère et devient trop petit, un regex-replace fera la plupart du travail de le transformer en une else ifchaîne.
personne le
6
La question initiale (il y a trois ans!) Demandait juste des avantages et des inconvénients entre if / else et switch. Ceci est un exemple. J'ai personnellement vu ce type d'optimisation faire une différence significative dans l'exécution d'une routine.
Mark Bessey
7

souvent, il sera plus joli, c'est-à-dire qu'il sera plus facile de comprendre ce qui se passe. Étant donné que les performances seront au mieux extrêmement minimes, la vue du code est la différence la plus importante.

Donc, si le if / else semble meilleur, utilisez-le, sinon utilisez une instruction switch.

gbjbaanb
la source
4

Sujet secondaire, mais je m'inquiète souvent (et je le vois plus souvent) if/ elseetswitch déclaration devient trop volumineuse avec trop de cas. Celles-ci nuisent souvent à la maintenabilité.

Les coupables communs comprennent:

  1. Faire trop à l'intérieur de plusieurs instructions if
  2. Plus de déclarations de cas qu'il n'est humainement possible d'analyser
  3. Trop de conditions dans l'évaluation if pour savoir ce qui est recherché

Pour corriger:

  1. Extraire en refactorisation de méthode.
  2. Utilisez un dictionnaire avec des pointeurs de méthode au lieu d'un cas, ou utilisez un IoC pour plus de configurabilité. Les usines de méthodes peuvent également être utiles.
  3. Extraire les conditions à leur propre méthode
Chris Brandsma
la source
4

Selon ce lien, la comparaison IF vs Switch du test d'itération en utilisant l'instruction switch et if, est similaire à 1 000 000 000 d'itérations, le temps pris par l' instruction Switch = 43,0s et par l' instruction If = 48,0s

Qui est littéralement 20833333 itérations par seconde. Donc, devons-nous vraiment nous concentrer davantage,

PS: Juste pour connaître la différence de performances pour une petite liste de conditions.

Bretfort
la source
Cela me suffit.
Greg Gum
3

Si vous utilisez simplement l'instruction if ou else, la solution de base utilise la comparaison? opérateur

(value == value1) ? (type1)do this : (type1)or do this;

Vous pouvez faire la routine ou dans un commutateur

switch(typeCode)
{
   case TypeCode:Int32:
   case TypeCode.Int64:
     //dosomething here
     break;
   default: return;
}
Robert W.
la source
2

Cela ne répond pas réellement à votre question, mais étant donné qu'il y aura peu de différence entre les versions compilées, je vous exhorte à écrire votre code d'une manière qui décrit le mieux vos intentions. Non seulement il y a de meilleures chances que le compilateur fasse ce que vous attendez, mais il sera plus facile pour les autres de maintenir votre code.

Si votre intention est de brancher votre programme en fonction de la valeur d'une variable / attribut, alors une instruction switch représente le mieux cette intention.

Si votre intention est de brancher votre programme en fonction de différentes variables / attributs / conditions, alors une chaîne if / else if représente le mieux cette intention.

J'accorderai que cody a raison à propos des gens qui oublient la commande break, mais presque aussi souvent je vois des gens faire des blocs compliqués où ils se trompent {}, donc les lignes qui devraient être dans l'instruction conditionnelle ne le sont pas. C'est l'une des raisons pour lesquelles j'inclus toujours {} dans mes instructions if, même s'il y a une ligne. Non seulement il est plus facile à lire, mais si j'ai besoin d'ajouter une autre ligne au conditionnel, je ne peux pas oublier de l'ajouter.

dj_segfault
la source
2

Question d'intérêt. Cela est arrivé il y a quelques semaines au travail et nous avons trouvé une réponse en écrivant un exemple d'extrait et en le visualisant dans .NET Reflector (le réflecteur est génial !! je l'adore).

Voici ce que nous avons découvert: une instruction switch valide pour autre chose qu'une chaîne est compilée en IL en tant qu'instruction switch. Cependant SI c'est une chaîne, elle est réécrite en if / else if / else en IL. Donc, dans notre cas, nous voulions savoir comment les instructions de commutateur comparent les chaînes, par exemple, sont sensibles à la casse, etc. et le réflecteur nous a rapidement donné une réponse. C'était utile de savoir.

Si vous souhaitez effectuer une comparaison sensible à la casse sur les chaînes, vous pouvez utiliser une instruction switch car elle est plus rapide que d'effectuer un String.Compare dans un if / else. (Edit: Read What is quicker, switch on string or elseif on type? For some actual performance tests) Cependant, si vous vouliez faire une insensibilité à la casse, il est préférable d'utiliser un if / else car le code résultant n'est pas joli.

switch (myString.ToLower())
{
  // not a good solution
}

La meilleure règle d'or consiste à utiliser des instructions switch si cela a du sens (sérieusement), par exemple:

  • il améliore la lisibilité de votre code
  • vous comparez une plage de valeurs (float, int) ou une énumération

Si vous devez manipuler la valeur à alimenter dans l'instruction switch (créer une variable temporaire contre laquelle basculer), vous devriez probablement utiliser une instruction de contrôle if / else.

Une mise à jour:

Il est en fait préférable de convertir la chaîne en majuscules (par exemple ToUpper()) car cela a été apparemment il y a d'autres optimisations que le compilateur juste à temps peut faire comme par rapport au ToLower(). Il s'agit d'une micro optimisation, mais en boucle serrée, cela pourrait être utile.


Une petite note:

Pour améliorer la lisibilité des instructions switch, essayez ce qui suit:

  • mettre la branche la plus probable en premier, c'est-à-dire la plus consultée
  • s'ils sont tous susceptibles de se produire, énumérez-les par ordre alphabétique afin de les retrouver plus facilement.
  • n'utilisez jamais le fourre-tout par défaut pour la dernière condition restante, c'est paresseux et causera des problèmes plus tard dans la vie du code.
  • utilisez le fourre-tout par défaut pour affirmer une condition inconnue même s'il est très peu probable qu'elle se produise. c'est pour cela que les affirmations sont bonnes.
Dennis
la source
Dans de nombreux cas, l'utilisation de ToLower () est la bonne solution, surtout s'il y a beaucoup de cas et que la table de hachage est générée.
Blaisorblade
"Si vous devez manipuler la valeur à alimenter dans l'instruction switch (créer une variable temporaire contre laquelle basculer), vous devriez probablement utiliser une instruction de contrôle if / else." - Bon conseil, merci.
Sneakyness
2

L'instruction switch est certainement la plus rapide puis un if if if. Il y a des tests de vitesse qui ont été fournis par BlackWasp

http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

--Vérifiez-le

Mais cela dépend fortement des possibilités que vous essayez de prendre en compte, mais j'essaie d'utiliser une instruction switch dans la mesure du possible.

traîneau
la source
1

Pas seulement C #, mais tous les langages basés sur C, je pense: parce qu'un commutateur est limité aux constantes, il est possible de générer du code très efficace en utilisant un "jump table". Le cas C est vraiment un bon vieux GOTO calculé FORTRAN, mais le cas C # est toujours testé contre une constante.

Ce n'est pas le cas que l'optimiseur pourra faire le même code. Considérez, par exemple,

if(a == 3){ //...
} else if (a == 5 || a == 7){ //...
} else {//...
}

Parce que ce sont des booléens composés, le code généré doit calculer une valeur et un court-circuit. Considérons maintenant l'équivalent

switch(a){
   case 3: // ...
    break;
   case 5:
   case 7: //...
    break;
   default: //...
}

Cela peut être compilé en

BTABL: *
B3:   addr of 3 code
B5:
B7:   addr of 5,7 code
      load 0,1 ino reg X based on value
      jump indirect through BTABL+x

parce que vous dites implicitement au compilateur qu'il n'a pas besoin de calculer les tests OR et d'égalité.

Charlie Martin
la source
Il n'y a aucune raison qu'un bon optimiseur ne puisse pas gérer le 1er code, tant que l'optim est implémenté. "Le compilateur ne peut pas optimiser" dépend uniquement des différences sémantiques que seul l'humain peut concilier (c'est-à-dire si f () est appelé, il ne sait pas que f () renvoie toujours 0 ou 1).
Blaisorblade
0

Mon professeur CS a suggéré de ne pas changer de déclaration, car si souvent les gens oublient la pause ou l'utilisent incorrectement. Je ne peux pas me souvenir exactement de ce qu'il a dit, mais quelque chose dans le sens où en regardant une base de code séminale qui montrait des exemples de la déclaration switch (il y a des années) avait aussi des tonnes d'erreurs.


la source
Pas vraiment un problème en C #. Voir: stackoverflow.com/questions/174155/… ... et lisez également stackoverflow.com/questions/188461/… pour une discussion sur pourquoi vivre dans la peur n'est peut-être pas la meilleure politique ...
Shog9
0

Quelque chose que je viens de remarquer est que vous pouvez combiner les instructions if / else et switch! Très utile pour vérifier les conditions préalables.

if (string.IsNullOrEmpty(line))
{
    //skip empty lines
}
else switch (line.Substring(0,1))
{
    case "1":
        Console.WriteLine(line);
        break;
    case "9":
        Console.WriteLine(line);
        break;
    default:
        break;
}
Même Mien
la source
3
Je sais que c'est vieux, mais je pense que techniquement vous ne "combinez" rien. Chaque fois que vous avez un "autre" sans les accolades, l'instruction suivante sera exécutée. Cette instruction peut être une instruction d'une ligne, généralement indiquée en retrait sur la ligne suivante, ou des instructions composites comme c'est le cas avec if, switch, using, lock, etc. En d'autres termes, vous pouvez avoir "else if", " else switch "," else using ", etc. Cela dit, j'aime bien à quoi cela ressemble et semble presque intentionnel. (Avertissement: je n'ai pas essayé tout cela donc je peux avoir tort!)
Nelson Rothermel
Nelson, vous avez 100% raison. J'ai compris pourquoi cela se produit après avoir posté cette réponse.
Even Mien
0

Je pense que Switch est plus rapide que si des conditions comme voir s'il existe un programme comme:

Écrivez un programme pour entrer n'importe quel nombre (entre 1 - 99) et vérifiez qu'il se trouve dans quel emplacement a) 1 - 9 puis emplacement un b) 11 - 19 puis emplacement deux c) 21-29 puis emplacement trois et ainsi de suite jusqu'à 89- 99

Ensuite, si vous devez faire de nombreuses conditions, mais que vous devez taper le boîtier de commutation de fils

Interrupteur (non / 10)

et sur le cas 0 = 1-9, le cas 1 = 11-19 et ainsi de suite

ce sera si facile

Il existe également de nombreux autres exemples!

Communauté
la source
0

une instruction switch est fondamentalement une comparaison pour l'égalité. Les événements clavier ont un grand avantage sur les instructions switch lorsqu'ils ont du code facile à écrire et à lire, alors une instruction if elseif le ferait, manquer un {bracket} pourrait également devenir problématique.

char abc;
switch(abc)
{
case a: break;
case b: break;
case c: break;
case d: break;
}

Une instruction if elseif est idéale pour plus d'une solution si (theAmountOfApples est supérieur à 5 && theAmountOfApples est inférieur à 10) enregistrez vos pommes ailleurs si (theAmountOfApples est supérieur à 10 || theAmountOfApples == 100) vendez vos pommes. Je n'écris pas c # ou c ++ mais je l'ai appris avant d'apprendre java et ce sont des langues proches.

Geen
la source
0

Un inconvénient possible des déclarations de commutateur est son manque de conditions multiples. Vous pouvez avoir plusieurs conditions pour les instructions if (else) mais pas plusieurs cases avec des conditions différentes dans un commutateur.

Les instructions de commutation ne conviennent pas aux opérations logiques au-delà de la portée des équations / expressions booléennes simples. Pour ces équations / expressions booléennes, il convient parfaitement mais pas pour d'autres opérations logiques.

Vous avez beaucoup plus de liberté avec la logique disponible dans les instructions If, mais la lisibilité peut en souffrir si l'instruction If devient difficile à manier ou est mal gérée.

Les deux ont leur place selon le contexte de ce à quoi vous êtes confronté.

Neil Meyer
la source