Domanda

L'affermazione dell'istruzione switch è una delle mie principali ragioni personali per amare i costrutti switch vs. if / else if . Un esempio è qui:

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

Le persone intelligenti stanno facendo il cretino perché le string [] dovrebbero essere dichiarate al di fuori della funzione: beh, lo sono, questo è solo un esempio.

Il compilatore ha esito negativo con il seguente errore:

Control cannot fall through from one case label ('case 3:') to another
Control cannot fall through from one case label ('case 2:') to another

Perché? E c'è un modo per ottenere questo tipo di comportamento senza avere tre if s?

È stato utile?

Soluzione

(Copia / incolla di una risposta che ho fornito altrove )

Il passaggio tra switch - case può essere ottenuto senza un codice in un case (vedi case 0 ) o utilizzando lo speciale goto case (vedi case 1 ) o goto default (vedi case 2 ) forme:

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

Altri suggerimenti

Il " perché " è evitare cadute accidentali, per le quali sono grato. Questa è una fonte non insolita di bug in C e Java.

La soluzione alternativa consiste nell'utilizzare goto, ad esempio

switch (number.ToString().Length)
{
    case 3:
        ans += string.Format("{0} hundred and ", numbers[number / 100]);
        goto case 2;
    case 2:
    // Etc
}

Il design generale di switch / case è un po 'sfortunato dal mio punto di vista. Si è bloccato troppo vicino a C - ci sono alcune modifiche utili che potrebbero essere fatte in termini di scoping ecc. Probabilmente sarebbe utile un interruttore più intelligente che potrebbe fare corrispondenze di pattern ecc., Ma questo sta davvero cambiando dal passaggio a "controlla una sequenza di condizioni" ; - a quel punto si potrebbe forse chiedere un nome diverso.

Il passaggio all'errore è storicamente una delle principali fonti di bug nei software moderni. Il progettista della lingua ha deciso di rendere obbligatorio il salto alla fine del caso, a meno che non si stia passando automaticamente al caso successivo senza elaborarlo.

switch(value)
{
    case 1:// this is still legal
    case 2:
}

Per aggiungere qui le risposte, penso che valga la pena considerare la domanda opposta in combinato disposto con questo, vale a dire. perché C ha permesso il fall-through in primo luogo?

Qualsiasi linguaggio di programmazione ovviamente ha due obiettivi:

  1. Fornisci istruzioni al computer.
  2. Lascia un registro delle intenzioni del programmatore.

La creazione di qualsiasi linguaggio di programmazione è quindi un equilibrio tra come servire al meglio questi due obiettivi. Da un lato, più è facile trasformarsi in istruzioni per computer (che si tratti di codice macchina, bytecode come IL o istruzioni interpretate in fase di esecuzione), quindi più tale processo di compilazione o interpretazione sarà efficiente, affidabile e compatto in uscita. Portato all'estremo, questo obiettivo si traduce nella nostra semplice scrittura in assembly, IL, o anche in codici operativi grezzi, perché la compilazione più semplice è dove non esiste alcuna compilazione.

Al contrario, più la lingua esprime l'intenzione del programmatore, piuttosto che i mezzi presi a tal fine, più comprensibile il programma sia durante la scrittura che durante la manutenzione.

Ora, switch avrebbe sempre potuto essere compilato convertendolo nella catena equivalente di blocchi if-else o simili, ma è stato progettato per consentire la compilazione in un particolare schema di assemblaggio comune in cui si prende un valore, calcola un offset da esso (sia guardando una tabella indicizzata da un hash perfetto del valore, sia dall'aritmetica effettiva sul valore *). Vale la pena notare a questo punto che oggi la compilazione C # a volte trasforma switch nell'equivalente if-else , e talvolta usa un approccio di salto basato sull'hash (e allo stesso modo con C , C ++ e altre lingue con sintassi comparabile).

In questo caso ci sono due buoni motivi per consentire il fall-through:

  1. Succede comunque in modo naturale: se si crea una tabella di salto in un set di istruzioni e uno dei precedenti lotti di istruzioni non contiene una sorta di salto o ritorno, l'esecuzione si evolverà naturalmente in il prossimo lotto. Consentire il fall-through era ciò che sarebbe semplicemente accaduto " se hai trasformato l'interruttore usando C in tabella di salto & # 8211; usando il codice macchina.

  2. I programmatori che scrivevano in assembly erano già usati per l'equivalente: quando scrivevano una tabella di salto a mano in assembly, avrebbero dovuto considerare se un dato blocco di codice sarebbe terminato con un ritorno, un salto al di fuori del tabella, o semplicemente continuare con il blocco successivo. Come tale, avere il programmatore aggiungere una break esplicita quando necessario era "naturale" anche per il programmatore.

All'epoca, quindi, era un ragionevole tentativo di bilanciare i due obiettivi di un linguaggio informatico in relazione sia al codice macchina prodotto sia all'espressività del codice sorgente.

Quattro decenni dopo, però, le cose non sono esattamente le stesse, per alcuni motivi:

  1. I programmatori in C oggi potrebbero avere poca o nessuna esperienza di assemblaggio. I programmatori in molti altri linguaggi in stile C hanno anche meno probabilità di (specialmente Javascript!). Qualsiasi concetto di "cosa le persone sono abituate dall'assemblaggio" non è più pertinente.
  2. I miglioramenti nelle ottimizzazioni significano che la probabilità di switch viene trasformata in if-else perché è stato ritenuto l'approccio probabilmente più efficiente, oppure trasformato in un le varianti particolarmente esoteriche dell'approccio con tabella di salto sono più elevate. La mappatura tra gli approcci di livello superiore e inferiore non è così forte come una volta.
  3. L'esperienza ha dimostrato che il fall-through tende a essere il caso di minoranza piuttosto che la norma (uno studio del compilatore di Sun ha rilevato che il 3% dei blocchi switch utilizzava un fall-through diverso da più etichette sul stesso blocco, e si pensava che il caso d'uso qui significasse che questo 3% era in realtà molto più alto del normale). Quindi la lingua studiata rende l'insolito più facilmente accessibile rispetto al comune.
  4. L'esperienza ha dimostrato che il fall-through tende ad essere la fonte di problemi sia nei casi in cui viene effettuato accidentalmente, sia nei casi in cui un fall-through corretto viene perso da qualcuno che mantiene il codice. Quest'ultima è una sottile aggiunta ai bug associati al fall-through, perché anche se il tuo codice è perfettamente privo di bug, il fall-through può comunque causare problemi.

In relazione a questi ultimi due punti, considera la seguente citazione dall'edizione corrente di K & amp; R:

  

Il passaggio da un caso all'altro non è solido, essendo soggetto a disintegrazione quando il programma viene modificato. Ad eccezione di più etichette per un singolo calcolo, i fall-through dovrebbero essere usati con parsimonia e commentati.

     

In buona forma, fai una pausa dopo l'ultimo caso (il default qui) anche se logicamente non è necessario. Un giorno, quando un altro caso verrà aggiunto alla fine, questo pezzetto di programmazione difensiva ti salverà.

Quindi, dalla bocca del cavallo, cadere in C è problematico. È buona norma documentare sempre i fallimenti con i commenti, che è un'applicazione del principio generale secondo cui si dovrebbe documentare dove si fa qualcosa di insolito, perché è quello che farà scattare l'esame successivo del codice e / o rendere il codice simile ha un bug per principianti quando è effettivamente corretto.

E quando ci pensi, codice come questo:

switch(x)
{
  case 1:
   foo();
   /* FALLTHRU */
  case 2:
    bar();
    break;
}

Sta aggiungendo qualcosa per rendere esplicito il fall-through nel codice, non è semplicemente qualcosa che può essere rilevato (o la cui assenza può essere rilevata) dal compilatore.

In quanto tale, il fatto che on debba essere esplicito con fall-through in C # non aggiunge alcuna penalità alle persone che hanno scritto bene in altri linguaggi C-style, poiché sarebbero già esplicite nelle loro fall-through . & # 8224;

Infine, l'uso di goto qui è già una norma da C e altre lingue simili:

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

In questo tipo di casi in cui vogliamo che un blocco sia incluso nel codice eseguito per un valore diverso da quello che porta uno al blocco precedente, allora dobbiamo già usare goto . (Naturalmente, ci sono mezzi e modi per evitarlo con diversi condizionali, ma questo è vero per tutto ciò che riguarda questa domanda). Pertanto, C # si è basato sul modo già normale di affrontare una situazione in cui vogliamo colpire più di un blocco di codice in un switch , e lo ha appena generalizzato per coprire anche il fall-through. Ha anche reso entrambi i casi più convenienti e autocompattanti, poiché dobbiamo aggiungere una nuova etichetta in C ma possiamo usare case come etichetta in C #. In C # possiamo eliminare l'etichetta below_six e usare goto case 5 che è più chiaro su ciò che stiamo facendo. (Dovremmo anche aggiungere break per il default , che ho lasciato fuori solo per rendere chiaramente il codice C sopra non il codice C #).

In sintesi quindi:

  1. C # non si riferisce più all'output del compilatore non ottimizzato direttamente come ha fatto il codice C 40 anni fa (né C in questi giorni), il che rende irrilevante una delle ispirazioni del fall-through.
  2. C # rimane compatibile con C non avendo solo break implicito, per un più facile apprendimento della lingua da parte di chi ha familiarità con lingue simili e un porting più semplice.
  3. C # rimuove una possibile fonte di bug o codice incompreso che è stato ben documentato come causa di problemi negli ultimi quattro decenni.
  4. C # rende le best practice esistenti con C (il documento fallisce) applicabile dal compilatore.
  5. C # rende il caso insolito quello con un codice più esplicito, il solito caso quello con il codice che scrive appena automaticamente.
  6. C # usa lo stesso approccio basato su goto per colpire lo stesso blocco da etichette case diverse come viene usato in C. Lo generalizza solo in altri casi.
  7. C # rende l'approccio basato su goto più conveniente e più chiaro rispetto a quello in C, consentendo alle istruzioni case di fungere da etichette.

Tutto sommato, una decisione di progetto abbastanza ragionevole


* Alcune forme di BASIC consentirebbero di fare simili a GOTO (x AND 7) * 50 + 240 che, sebbene fragili e quindi un caso particolarmente convincente per vietare goto , serve a mostrare un equivalente di lingua superiore del modo in cui il codice di livello inferiore può fare un salto basato sull'aritmetica su un valore, che è molto più ragionevole quando è il risultato della compilazione piuttosto che qualcosa che deve essere gestito manualmente. Le implementazioni del dispositivo Duff in particolare si prestano bene all'equivalente codice macchina o IL perché ogni blocco di istruzioni avrà spesso la stessa lunghezza senza la necessità di aggiungere riempitori nop .

& # 8224; Il dispositivo di Duff torna di nuovo qui, come una ragionevole eccezione. Il fatto che con questo e simili schemi vi sia una ripetizione di operazioni serve a rendere l'uso del fall-through relativamente chiaro anche senza un commento esplicito in tal senso.

Puoi "andare all'etichetta del caso" http://www.blackwasp.co.uk/CSharpGoto.aspx

  

L'istruzione goto è un semplice comando che trasferisce incondizionatamente il controllo del programma su un'altra istruzione. Il comando viene spesso criticato con alcuni sviluppatori che sostengono la sua rimozione da tutti i linguaggi di programmazione di alto livello perché può portare a < em> codice spaghetti . Ciò si verifica quando ci sono così tante dichiarazioni goto o istruzioni jump simili che il codice diventa difficile da leggere e mantenere. Tuttavia, ci sono programmatori che sottolineano che l'istruzione goto, se usata con attenzione, fornisce una soluzione elegante ad alcuni problemi ...

Hanno lasciato fuori questo comportamento dalla progettazione per evitare che non fosse usato dalla volontà ma causasse problemi.

Può essere usato solo se non ci sono istruzioni nella parte del caso, come:

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}

Hanno cambiato il comportamento dell'istruzione switch (da C / Java / C ++) per c #. Immagino che il ragionamento fosse che le persone si sono dimenticate della caduta e che sono stati causati errori. Un libro che ho letto diceva di usare goto per simulare, ma per me non sembra una buona soluzione.

  

Un'istruzione jump come un'interruzione è   richiesto dopo ogni blocco di casi,   incluso l'ultimo blocco, sia esso   un'istruzione case o un valore predefinito   dichiarazione. Con un'eccezione (a differenza di   l'istruzione switch C ++), C # no   sostenere una caduta implicita da   un'etichetta caso per un altro. L'unico   l'eccezione è se un'istruzione case ha   nessun codice.

- Documentazione C # switch ()

Dopo ogni istruzione del caso è necessario interrompere o vai anche se si tratta di un caso predefinito.

Puoi ottenere fall come c ++ con la parola chiave 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;
}

Solo una breve nota per aggiungere che il compilatore per Xamarin ha effettivamente sbagliato e consente il fallthrough. Presumibilmente è stato corretto, ma non è stato rilasciato. L'ho scoperto in un codice che in realtà stava fallendo e il compilatore non si è lamentato.

C # non supporta fall fall con istruzioni switch / case. Non so perché, ma non c'è davvero supporto per questo. Linkage

switch (riferimento C #) dice

  

C # richiede la fine delle sezioni degli switch, incluso l'ultimo,

Quindi devi anche aggiungere una break; alla tua sezione default , altrimenti ci sarà ancora un errore del compilatore.

Hai dimenticato di aggiungere l'opzione " break; " nell'istruzione case 3. Nel caso 2 l'hai scritta nel blocco if. Quindi prova questo:

case 3:            
{
    ans += string.Format("{0} hundred and ", numbers[number / 100]);
    break;
}


case 2:            
{
    int t = (number / 10) % 10;            
    if (t == 1)            
    {                
        ans += teens[number % 10];                
    }            
    else if (t > 1)                
    {
        ans += string.Format("{0}-", tens[t]);        
    }
    break;
}

case 1:            
{
    int o = number % 10;            
    ans += numbers[o];            
    break;        
}

default:            
{
    throw new ArgumentException("number");
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top