C'è qualche differenza significativa tra l'utilizzo di if / else e switch-case in C #?


219

Qual è il vantaggio / svantaggio dell'uso di una switchdichiarazione rispetto a if/elsein C #. Non riesco a immaginare che ci sia una differenza così grande, a parte forse l'aspetto del tuo codice.

C'è qualche motivo per cui l'IL risultante o le prestazioni di runtime associate sarebbero radicalmente diverse?

Correlati: che cos'è più veloce, attiva la stringa o altro se digita?



3
Questa domanda è interessante solo in teoria per la maggior parte degli sviluppatori, a meno che non ti trovi spesso a ripetere un MILIONE di volte. (Quindi usa un'istruzione switch e passa da 48 a 43 secondi ...) O nelle parole di Donald Knuth: "Dovremmo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali" it.wikipedia.org/wiki/Program_optimization#When_to_optimize
Sire

Uso spesso if / else invece di switch a causa dell'ambito scoping condiviso di switch.
Josh,

Risposte:


341

L'istruzione SWITCH produce solo lo stesso assieme degli IF in modalità debug o compatibilità. Nel rilascio, verrà compilato nella tabella di salto (tramite l'istruzione 'switch' di MSIL) - che è O (1).

C # (a differenza di molte altre lingue) consente anche di attivare le costanti di stringa - e questo funziona in modo leggermente diverso. Ovviamente non è pratico costruire tabelle di salto per stringhe di lunghezze arbitrarie, quindi molto spesso tale switch verrà compilato in stack di IF.

Ma se il numero di condizioni è abbastanza grande da coprire le spese generali, il compilatore C # creerà un oggetto HashTable, lo popolerà con costanti di stringa e farà una ricerca su quella tabella seguita da jump. La ricerca hashtable non è strettamente O (1) e presenta costi costanti evidenti, ma se il numero di etichette dei casi è grande, sarà significativamente più veloce rispetto al confronto con ciascuna costante di stringa negli IF.

Per riassumere, se il numero di condizioni è superiore a 5 o giù di lì, preferisci SWITCH rispetto a IF, altrimenti usa qualunque cosa appaia migliore.


1
Sei sicuro che il compilatore C # produrrà una tabella hash? Il punto che ho fatto riguardo alla tabella hash nella nostra discussione di commento sopra riguardava i compilatori nativi, non il compilatore C #. Quale soglia utilizza il compilatore C # per produrre una tabella hash?
Scott Wisniewski il

8
Circa dieci penso. 20 per essere al sicuro. E a proposito, la mia rabbia non sei tu, ma sulle persone che votano e accettano.
ima,

48
Un po 'di sperimentazione suggerisce il conteggio <= 6: "if"; conteggio> = 7: dizionario. Questo è con il compilatore MS .NET 3.5 C # - potrebbe cambiare tra versioni e fornitori, ovviamente.
Jon Skeet,

37
Ehi, ti devo delle scuse. Scusa per essere una testa d'osso.
Scott Wisniewski il

Come follow-up, per applicazioni pratiche, c'è qualche differenza nel mondo reale per la maggior parte del tempo? Trovo che le istruzioni switch in C # siano strane, la loro sintassi non è abbastanza come qualsiasi altra cosa e trovo che rendono il mio codice meno leggibile, vale la pena preoccuparsi di usare le istruzioni switch, o dovrei semplicemente programmare con ifs altro e venire solo indietro e sostituirli se colpisco colli di bottiglia delle prestazioni?
Jason Masters il

54

In generale (considerando tutte le lingue e tutti i compilatori) un'istruzione switch PU CAN ALCUNI EFFETTI essere più efficiente di un'istruzione if / else, poiché è facile per un compilatore generare tabelle di salto da istruzioni switch. È possibile fare la stessa cosa per le istruzioni if ​​/ else, dati i vincoli appropriati, ma è molto più difficile.

Nel caso di C #, anche questo è vero, ma per altri motivi.

Con un gran numero di stringhe, si ha un notevole vantaggio in termini di prestazioni nell'uso di un'istruzione switch, poiché il compilatore utilizzerà una tabella hash per implementare il salto.

Con un numero limitato di stringhe, le prestazioni tra i due sono le stesse.

Questo perché in quel caso il compilatore C # non genera una tabella di salto. Invece genera MSIL che equivale a blocchi IF / ELSE.

Esiste un'istruzione MSIL "switch statement" che, quando viene eseguita il jitter, utilizzerà una tabella di salto per implementare un'istruzione switch. Funziona solo con tipi interi, tuttavia (questa domanda fa domande sulle stringhe).

Per un numero limitato di stringhe, è più efficiente per il compilatore generare blocchi IF / ELSE che utilizzare una tabella hash.

Quando l'ho notato inizialmente, ho ipotizzato che, poiché i blocchi IF / ELSE erano usati con un piccolo numero di stringhe, che il compilatore avesse fatto la stessa trasformazione per un gran numero di stringhe.

Questo era SBAGLIATO. "IMA" è stato così gentile da segnalarmelo (beh ... non era gentile, ma aveva ragione, e io avevo torto, che è la parte importante)

Ho anche formulato un'ipotesi sulla mancanza di un'istruzione "switch" in MSIL (ho pensato che se ci fosse un primitivo switch, perché non lo stavano usando con una tabella hash, quindi non ci dovrebbe essere un primitivo switch. ...). Questo è stato sia sbagliato, sia incredibilmente stupido da parte mia. Ancora una volta "IMA" me lo ha fatto notare.

Ho fatto gli aggiornamenti qui perché è il post più votato e la risposta accettata.

Tuttavia, l'ho reso Community Wiki perché immagino di non meritare il REP per aver sbagliato. Se ne hai la possibilità, per favore, vota il post di "ima".


3
C'è una primitiva di switch in MSIL e le istruzioni c # vengono compilate in una ricerca generalmente simile a C. In determinate circostanze (piattaforma di destinazione, switch cl ecc.) Lo switch può essere espanso in IF durante la compilazione, ma è solo una misura di compatibilità di fallback.
ima,

6
Tutto quello che posso fare è scusarmi per aver fatto uno stupido errore. Credimi, mi sento stupido. Scherzi a parte, penso che sia ancora la risposta migliore. Nei compilatori nativi è possibile utilizzare una tabella hash per implementare un salto, quindi questa non è una cosa orribilmente sbagliata. Ho fatto un errore.
Scott Wisniewski il

9
ima, se ci sono errori, segnalali. Sembra che Scott sarà felice di correggere il post. In caso contrario, altri che hanno raggiunto la capacità di correggere una risposta lo faranno. Questo è l'unico modo in cui un sito come questo funzionerà e, in generale, sembra funzionare. Oppure prendi la palla e torna a casa :)
jwalkerjr,

2
@Scott: ti incoraggio a modificare il secondo e il terzo paragrafo per dichiarare esplicitamente "per le stringhe". Le persone potrebbero non leggere l'aggiornamento in fondo.
Jon Skeet,

4
@ima Se pensi che una risposta sia obiettivamente sbagliata, modificala per essere corretta. Ecco perché tutti possono modificare le risposte.
Miles Rout,

18

Tre motivi per preferire switch:

  • Un compilatore che prende di mira il codice nativo può spesso compilare un'istruzione switch in un ramo condizionale più un salto indiretto mentre una sequenza di ifs richiede una sequenza di rami condizionali . A seconda della densità dei casi, sono stati scritti molti articoli su come compilare le dichiarazioni dei casi in modo efficiente; alcuni sono collegati dalla pagina del compilatore lcc . (Lcc aveva uno dei compilatori più innovativi per switch.)

  • Un'istruzione switch è una scelta tra alternative che si escludono a vicenda e la sintassi dello switch rende questo flusso di controllo più trasparente per il programmatore, quindi un nido di istruzioni if-then-else.

  • In alcune lingue, tra cui sicuramente ML e Haskell, il compilatore controlla se hai lasciato fuori dei casi . Considero questa funzione uno dei maggiori vantaggi di ML e Haskell. Non so se C # può farlo.

Un aneddoto: a una lezione che tenne dopo aver ricevuto un premio per la carriera, sentii Tony Hoare dire che di tutte le cose che aveva fatto nella sua carriera, ce n'erano tre di cui era più orgoglioso:

  • Inventare Quicksort
  • Inventare l'istruzione switch (che Tony ha chiamato l' caseistruzione)
  • Inizia e termina la sua carriera nel settore

Non riesco a immaginare di vivere senzaswitch .


16

Il compilatore ottimizzerà praticamente tutto nello stesso codice con differenze minori (Knuth, chiunque?).

La differenza è che un'istruzione switch è più pulita di quindici se altre istruzioni messe insieme.

Gli amici non consentono agli amici di impilare le dichiarazioni if-else.


13
"Gli amici non consentono agli amici di impilare le dichiarazioni if-else." Dovresti fare un sitcker da sballo :)
Matthew M. Osborn il

14

In realtà, un'istruzione switch è più efficiente. Il compilatore lo ottimizzerà in una tabella di ricerca in cui con istruzioni if ​​/ else non può. Il lato negativo è che un'istruzione switch non può essere utilizzata con valori variabili.
Non puoi fare:

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

deve essere

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

1
hai qualche tipo di numero? Sono curioso di sapere quanto bene un compilatore potrebbe ottimizzare un'istruzione swtich su un If / Else
Matthew M. Osborn,

Sì, credo che un'istruzione switch sia sempre ottimizzata su O (1) dove un'istruzione if else sarà O (n) dove n è la posizione del valore corretto nelle istruzioni if ​​/ else if.
kemiller2002,

Nel caso di C # questo non è vero, vedi il mio post qui sotto per maggiori informazioni.
Scott Wisniewski,

Non ne sono del tutto sicuro, ma non riesco a trovare le informazioni nel libro in cui giuro di averle trovate. Sei sicuro di non guardare il codice MSIL senza ottimizzazione. Non creerà la tabella di salto a meno che non si compili con l'ottimizzazione attiva.
kemiller2002,

Ho compilato sia in modalità debug che retail, e in entrambi i casi genera blocchi if / else. Sei sicuro che il libro che stavi guardando parlava di C #? Il libro probabilmente era un libro di compilazione o un libro su C o C ++
Scott Wisniewski,

14

Non ho visto nessun altro sollevare il punto (ovvio?) Secondo cui il presunto vantaggio in termini di efficienza dell'istruzione switch dipende dal fatto che i vari casi sono approssimativamente ugualmente probabili. Nei casi in cui uno (o alcuni) dei valori sono molto più probabili, la scala if-then-else può essere molto più veloce, assicurando che i casi più comuni siano controllati per primi:

Quindi, per esempio:

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

vs

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

Se x è zero il 90% delle volte, il codice "if-else" può essere due volte più veloce del codice basato su switch. Anche se il compilatore trasforma lo "switch" in una sorta di goto intelligente basato su una tabella, non sarà comunque veloce come il semplice controllo dello zero.


3
Nessuna ottimizzazione prematura! In generale, se hai più di pochi casi e sono switchcompatibili, l' switchaffermazione è migliore (più leggibile, a volte più veloce). Se sai che un caso è molto più probabile, puoi estrarlo per formare un if- else- switchcostrutto e se è misurabile in modo più veloce , lo lasci dentro. (Ripeti, se necessario.) IMO che è ancora ragionevolmente leggibile. Se switchdegenera e diventa troppo piccolo, un regex-sostituirà farà la maggior parte del lavoro di trasformarlo in una else ifcatena.
nessuno il

6
La domanda originale (tre anni fa!) Chiedeva solo vantaggi e svantaggi tra if / else e switch. Questo è un esempio Ho visto personalmente questo tipo di ottimizzazione fare una differenza significativa nel tempo di esecuzione di una routine.
Mark Bessey,

7

spesso avrà un aspetto migliore, ovvero sarà più facile capire cosa sta succedendo. Considerando che il vantaggio in termini di prestazioni sarà estremamente minimo, la vista del codice è la differenza più importante.

Quindi, se if / else sembra migliore, usalo, altrimenti usa un'istruzione switch.


4

Argomento secondario, ma spesso mi preoccupo (e più spesso vedo) if/ elseeswitch affermazione diventa troppo grande con troppi casi. Questi spesso danneggiano la manutenibilità.

I colpevoli comuni includono:

  1. Fare troppo all'interno di più istruzioni if
  2. Più dichiarazioni di casi che umanamente possibile analizzare
  3. Troppe condizioni nella valutazione if per sapere cosa si sta cercando

Aggiustare:

  1. Estratto del refactoring del metodo.
  2. Utilizzare un dizionario con puntatori a metodi anziché un caso oppure utilizzare un IoC per una maggiore configurabilità. Anche le fabbriche di metodi possono essere utili.
  3. Estrarre le condizioni secondo il proprio metodo

4

Secondo questo link, il confronto IF vs Switch del test di iterazione usando switch e if statement è simile a 1.000.000.000 di iterazioni, tempo impiegato dall'istruzione Switch = 43.0s e dall'istruzione If = 48.0s

Che è letteralmente 20833333 iterazioni al secondo, quindi, dovremmo davvero concentrarci di più,

PS: solo per conoscere la differenza di prestazioni per un piccolo elenco di condizioni.


Questo lo inchioda per me.
Greg Gum,

3

Se stai semplicemente usando l'istruzione if oppure else la soluzione di base sta usando la comparazione? operatore

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

È possibile eseguire la routine o in uno switch

switch(typeCode)
{
   case TypeCode:Int32:
   case TypeCode.Int64:
     //dosomething here
     break;
   default: return;
}

2

Questo in realtà non risponde alla tua domanda, ma dato che ci sarà poca differenza tra le versioni compilate, ti esorto a scrivere il tuo codice in un modo che descriva al meglio le tue intenzioni. Non solo c'è una migliore possibilità che il compilatore faccia quello che ti aspetti, ma renderà più facile agli altri mantenere il tuo codice.

Se la tua intenzione è quella di ramificare il tuo programma in base al valore di una variabile / attributo, un'istruzione switch rappresenta al meglio quell'intenzione.

Se la tua intenzione è quella di ramificare il tuo programma in base a variabili / attributi / condizioni differenti, allora una catena if / else if rappresenta al meglio quell'intenzione.

Concederò che il codice è giusto riguardo alle persone che dimenticano il comando break, ma quasi altrettanto spesso vedo le persone fare complicate se blocchi in cui sbagliano {}, quindi le righe che dovrebbero essere nell'istruzione condizionale no. È uno dei motivi per cui includo sempre {} nelle mie dichiarazioni if ​​anche se contiene una riga. Non solo è più facile da leggere, ma se devo aggiungere un'altra riga nel condizionale, non posso dimenticare di aggiungerlo.


2

Domanda di interesse. Questo è emerso alcune settimane fa al lavoro e abbiamo trovato una risposta scrivendo un frammento di esempio e visualizzandolo in .NET Reflector (reflector è fantastico !! lo adoro).

Questo è ciò che abbiamo scoperto: un'istruzione switch valida per qualsiasi cosa diversa da una stringa viene compilata in IL come un'istruzione switch. Tuttavia, se è una stringa, viene riscritta come if / else if / else in IL. Quindi nel nostro caso volevamo sapere come le istruzioni switch confrontano le stringhe, ad esempio la distinzione tra maiuscole e minuscole, ecc. E il riflettore ci ha dato rapidamente una risposta. Questo è stato utile sapere.

Se si desidera eseguire un confronto con distinzione tra maiuscole e minuscole sulle stringhe, è possibile utilizzare un'istruzione switch poiché è più veloce dell'esecuzione di String.Compare in un if / else. (Modifica: Leggi Che cosa è più veloce, attiva la stringa o elseif sul tipo? Per alcuni test prestazionali effettivi) Tuttavia, se volevi fare una distinzione tra maiuscole e minuscole, è meglio usare un if / else poiché il codice risultante non è carino.

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

La migliore regola empirica è usare le istruzioni switch se ha senso (sul serio), ad esempio:

  • migliora la leggibilità del tuo codice
  • stai confrontando un intervallo di valori (float, int) o un enum

Se è necessario manipolare il valore da inserire nell'istruzione switch (creare una variabile temporanea su cui passare), probabilmente si dovrebbe usare un'istruzione if / else control.

Un aggiornamento:

In realtà è meglio convertire la stringa in maiuscolo (es. ToUpper()) Dato che apparentemente ci sono state ulteriori ottimizzazioni che il compilatore just-in-time può fare rispetto a ToLower(). È una micro ottimizzazione, tuttavia in un ciclo stretto potrebbe essere utile.


Una piccola nota a margine:

Per migliorare la leggibilità delle istruzioni switch, provare quanto segue:

  • metti al primo posto il ramo più probabile, ovvero il più accessibile
  • se è probabile che si verifichino tutti, elencali in ordine alfabetico in modo che sia più facile trovarli.
  • non usare mai il catch-all predefinito per l'ultima condizione rimanente, che è pigro e causerà problemi in seguito nella vita del codice.
  • utilizzare il catch-all predefinito per affermare una condizione sconosciuta anche se è altamente improbabile che si verifichi mai. questo è ciò per cui le affermazioni vanno bene.

In molti casi l'uso di ToLower () è la soluzione giusta, specialmente se ci sono molti casi e viene generata la tabella hash.
Blaisorblade,

"Se hai bisogno di manipolare il valore da inserire nell'istruzione switch (crea una variabile temporanea su cui passare), probabilmente dovresti usare un'istruzione if / else control." - Un buon consiglio, grazie.
Sneakyness


1

Non solo C #, ma tutti i linguaggi basati su C, penso: poiché uno switch è limitato alle costanti, è possibile generare un codice molto efficiente usando una "tabella di salto". Il caso C è davvero un buon vecchio GOTO calcolato da FORTRAN, ma il caso C # è ancora testato contro una costante.

Non è il caso che l'ottimizzatore sarà in grado di creare lo stesso codice. Considera, ad esempio,

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

Poiché quelli sono booleani composti, il codice generato deve calcolare un valore e un cortocircuito. Ora considera l'equivalente

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

Questo può essere compilato in

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

perché stai implicitamente dicendo al compilatore che non è necessario calcolare i test OR e di uguaglianza.


Non vi è alcun motivo per cui un buon ottimizzatore non sia in grado di gestire il primo codice, a condizione che l'ottimizzazione sia implementata. "Il compilatore non può ottimizzare" dipende solo dalle differenze semantiche che solo l'umano può riconciliare (cioè se viene chiamato f (), non sa che f () restituisce sempre 0 o 1).
Blaisorblade,

0

Il mio professore di CS mi ha suggerito di non scambiare le dichiarazioni poiché così spesso le persone hanno dimenticato la pausa o l'usavano in modo errato. Non posso ricordare esattamente quello che ha detto, ma qualcosa sulla falsariga che guardando una base di codice seminale che mostrava esempi dell'istruzione switch (anni fa) aveva anche un sacco di errori.


Non è davvero un problema in C #. Vedi: stackoverflow.com/questions/174155/… ... e leggi anche stackoverflow.com/questions/188461/… per una discussione sul perché vivere nella paura potrebbe non essere la migliore politica ...
Shog9

0

Qualcosa che ho appena notato è che puoi combinare if / else e cambiare le istruzioni! Molto utile quando è necessario verificare i presupposti.

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;
}

3
So che questo è vecchio, ma penso che tecnicamente non stai "combinando" nulla. Ogni volta che hai un "altro" senza le parentesi graffe, verrà eseguita la frase successiva. Quell'istruzione potrebbe essere un'istruzione di una riga, in genere mostrata rientrata nella riga successiva, oppure istruzioni composte come nel caso di if, switch, using, lock, ecc. In altre parole, puoi avere "else if", " else switch "," else using ", ecc. Detto questo, mi piace come appare e sembra quasi intenzionale. (Dichiarazione di non responsabilità: non ho provato tutte queste, quindi POSSO sbagliarmi!)
Nelson Rothermel,

Nelson, hai ragione al 100%. Ho capito perché questo accade dopo aver pubblicato questa risposta.
Anche Mien,

0

Penso che Switch sia più veloce di se condizioni come vedere se esiste un programma come:

Scrivere un programma per inserire un numero qualsiasi (tra 1 - 99) e verificare che si trovi in ​​quale slot a) 1 - 9 quindi slot uno b) 11 - 19 quindi slot due c) 21-29 quindi slot tre e così via fino a 89- 99

Quindi, se devi creare molte condizioni, ma Son Switch Case, devi solo digitare

Interruttore (no / 10)

e nel caso 0 = 1-9, caso 1 = 11-19 e così via

sarà così facile

Ci sono anche molti altri esempi simili!


0

un'istruzione switch è fondamentalmente un confronto per l'uguaglianza. gli eventi da tastiera hanno un grande vantaggio rispetto alle istruzioni switch quando hanno un codice facile da scrivere e leggere, quindi un'istruzione if elseif, perdere un {parentesi} potrebbe anche creare problemi.

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

Un'istruzione if elseif è ottima per più di una soluzione se (theAmountOfApples è maggiore di 5 && theAmountOfApples è inferiore a 10) salva le tue mele altrimenti se (l'AmountOfApples è maggiore di 10 || theAmountOfApples == 100) vendi le tue mele. Non scrivo c # o c ++ ma l'ho imparato prima di imparare java e sono lingue vicine.


0

Un possibile aspetto negativo delle istruzioni switch è la mancanza di più condizioni. È possibile avere più condizioni per le istruzioni if ​​(else) ma non più casi con condizioni diverse in uno switch.

Le istruzioni switch non sono adatte per operazioni logiche oltre l'ambito delle semplici equazioni / espressioni booleane. Per quelle equazioni / espressioni booleane, è eminentemente adatto ma non per altre operazioni logiche.

Hai molta più libertà con la logica disponibile nelle istruzioni If ma la leggibilità può risentirne se l'istruzione if diventa ingombrante o viene gestita male.

Entrambi hanno posto a seconda del contesto di ciò che si sta affrontando.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.