Casi multipli nell'istruzione switch


582

Esiste un modo per scorrere più dichiarazioni di casi senza dichiarare case value:ripetutamente?

So che funziona:

switch (value)
{
   case 1:
   case 2:
   case 3:
      // Do some stuff
      break;
   case 4:
   case 5:
   case 6:
      // Do some different stuff
      break;
   default:
       // Default stuff
      break;
}

ma mi piacerebbe fare qualcosa del genere:

switch (value)
{
   case 1,2,3:
      // Do something
      break;
   case 4,5,6:
      // Do something
      break;
   default:
      // Do the Default
      break;
}

Questa sintassi mi viene in mente da un'altra lingua o mi manca qualcosa?


C'è un motivo per cui non usi solo un'istruzione IF (se stai controllando un intervallo di ints)?
Eric Schoonover,

2
si charlse, il primo modo funziona benissimo, l'ho usato in numerosi posti. È più sporco di quanto mi piacerebbe, ma è utile. Ho appena usato quegli interi come esempio. I dati reali erano più vari. Un if (1 || 2 || 3) {...} altrimenti if (4 || 5 || 6) {...} avrebbe funzionato troppo, ma è più difficile da leggere.
theo,

5
perché consideri il secondo più sporco del primo. Quest'ultimo aggiunge un altro significato a ,quello che non è condiviso con nessun altro linguaggio c-style. Mi sembrerebbe molto più sporco.
Jon Hanna,

1
Potresti aver raccolto la sintassi del 2 ° da Ruby. Funziona così in quella lingua (anche se switch diventa case, e case diventa quando, tra le altre cose)
juanpaco,

4
Nota importante . Gli intervalli sono supportati in caso di switch che avvii C # v7 - Vedi la risposta di
RBT

Risposte:


313

Non c'è sintassi in C ++ né C # per il secondo metodo che hai citato.

Non c'è niente di sbagliato nel tuo primo metodo. Se tuttavia hai intervalli molto grandi, basta usare una serie di istruzioni if.


5
Come aggiunta volevo aggiungere un link alla specifica del linguaggio C # disponibile su MSDN all'indirizzo msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
Richard McGuire,

L'utente può usare alcuni if ​​(o una ricerca di tabella) per ridurre l'input a un set di enumerazioni e attivare l'enum.
Harvey,

5
probabilmente l'ha scelto da VB.net
George Birbilis il

1
VB.net è meglio per varie dichiarazioni di casi ... tranne per il fatto che non passano come C #. Prendi un po ', dai un po'.
Brain2000,

Credo che questo non sia più corretto. Vedi stackoverflow.com/questions/20147879/… . Anche su questa domanda c'è una risposta stackoverflow.com/a/44848705/1073157
Dan Rayson

700

Immagino che sia già stata data una risposta. Tuttavia, penso che puoi ancora mescolare entrambe le opzioni in modo sintatticamente migliore facendo:

switch (value)
{
    case 1: case 2: case 3:          
        // Do Something
        break;
    case 4: case 5: case 6: 
        // Do Something
        break;
    default:
        // Do Something
        break;
}

3
'switch' dovrebbe essere minuscolo per c #?
Austin Harris,

3
Il codice compresso viene allungato al primo esempio nella domanda. Può anche farlo come è nella domanda.
MetalPhoenix,

10
Perché preoccuparsi? Il rientro automatico in Visual Studio 2013 ripristinerà comunque il formato nella domanda originale.
Gustav,

14
@T_D sta ricevendo supporto perché in realtà risponde alla domanda. L'OP ha detto, mi sto perdendo qualcosa ... Carlos ha risposto con quello che gli mancava. Sembra piuttosto tagliato e asciugato per me. Non odiare il fatto che abbia 422 voti positivi.
Mike Devenney,

8
@MikeDevenney Quindi hai interpretato la domanda in modo diverso, per quanto vedo la risposta corretta sarebbe "no, c # non ha alcuna sintassi per quello". Se qualcuno chiede "è possibile versare del liquido in un bicchiere che sto tenendo capovolto?" la risposta dovrebbe essere "no" e non "puoi versare liquido se lo guardi sottosopra e usi la tua immaginazione", perché questa risposta è tutta sull'uso dell'immaginazione. Se usi la sintassi normale ma la formatti male, sembra un'altra sintassi, con un po 'di immaginazione. Spero che tu ottenga il mio punto ...: P
T_D

74

Questa sintassi è dal Visual Basic Select ... Caso Dichiarazione :

Dim number As Integer = 8
Select Case number
    Case 1 To 5
        Debug.WriteLine("Between 1 and 5, inclusive")
        ' The following is the only Case clause that evaluates to True.
    Case 6, 7, 8
        Debug.WriteLine("Between 6 and 8, inclusive")
    Case Is < 1
        Debug.WriteLine("Equal to 9 or 10")
    Case Else
        Debug.WriteLine("Not between 1 and 10, inclusive")
End Select

Non è possibile utilizzare questa sintassi in C #. Invece, devi usare la sintassi del tuo primo esempio.


49
questa è una delle poche cose che mi mancano di * Basic.
Nickf

10
Qui abbiamo uno dei pochi display in cui visual basic non è così brutto ed è più versatile di c #. Questo è un esempio prezioso!
bgmCoder

Questo è abbastanza decente. Mi chiedo perché questo non sia stato aggiunto con C # 8.0. Sarebbe carino.
Sigex,

69

In C # 7 (disponibile per impostazione predefinita in Visual Studio 2017 / .NET Framework 4.6.2), la commutazione basata su intervallo è ora possibile con l' istruzione switch e aiuterebbe con il problema del PO.

Esempio:

int i = 5;

switch (i)
{
    case int n when (n >= 7):
        Console.WriteLine($"I am 7 or above: {n}");
        break;

    case int n when (n >= 4 && n <= 6 ):
        Console.WriteLine($"I am between 4 and 6: {n}");
        break;

    case int n when (n <= 3):
        Console.WriteLine($"I am 3 or less: {n}");
        break;
}

// Output: I am between 4 and 6: 5

Appunti:

  • Le parentesi (e )non sono richiesti nella whencondizione, ma sono usati in questo esempio per evidenziare il confronto (s).
  • varpuò anche essere usato al posto di int. Ad esempio: case var n when n >= 7:.

3
Questa (corrispondenza dei modelli) dovrebbe generalmente essere la migliore pratica quando è possibile utilizzare C # 7.xo versioni successive, poiché è molto più chiaro delle altre risposte.
Undying Jellyfish,

C'è un modo per raggiungere questo obiettivo con un elenco di Enum? Dove la mappa di Enums è int?
Sigex,

33

Puoi tralasciare la nuova riga che ti dà:

case 1: case 2: case 3:
   break;

ma considero quello stile cattivo.


20

.NET Framework 3.5 ha intervalli:

Enumerable.Range da MSDN

puoi usarlo con "contiene" e l'istruzione IF, poiché come qualcuno ha detto l'istruzione SWITCH utilizza l'operatore "==".

Ecco un esempio:

int c = 2;
if(Enumerable.Range(0,10).Contains(c))
    DoThing();
else if(Enumerable.Range(11,20).Contains(c))
    DoAnotherThing();

Ma penso che potremo divertirci di più: poiché non avrai bisogno dei valori di ritorno e questa azione non accetta parametri, puoi facilmente usare le azioni!

public static void MySwitchWithEnumerable(int switchcase, int startNumber, int endNumber, Action action)
{
    if(Enumerable.Range(startNumber, endNumber).Contains(switchcase))
        action();
}

Il vecchio esempio con questo nuovo metodo:

MySwitchWithEnumerable(c, 0, 10, DoThing);
MySwitchWithEnumerable(c, 10, 20, DoAnotherThing);

Dato che stai passando azioni, non valori, dovresti omettere la parentesi, è molto importante. Se hai bisogno di funzione con argomenti, basta cambiare il tipo di Actiona Action<ParameterType>. Se hai bisogno di valori di ritorno, usa Func<ParameterType, ReturnType>.

In C # 3.0 non esiste una semplice applicazione parziale per incapsulare il fatto che il parametro case sia lo stesso, ma si crea un piccolo metodo di supporto (un po 'dettagliato, comunque).

public static void MySwitchWithEnumerable(int startNumber, int endNumber, Action action){ 
    MySwitchWithEnumerable(3, startNumber, endNumber, action); 
}

Ecco un esempio di come le nuove dichiarazioni funzionali importate sono IMHO più potenti ed eleganti di quelle del vecchio imperativo.


3
Bella scelta. Una cosa da notare, però: Enumerable.Range ha argomenti int starte int count. I tuoi esempi non funzioneranno esattamente come sono stati scritti. Lo scrivi come se fosse il secondo argomento int end. Ad esempio - Enumerable.Range(11,20)comporterebbe 20 numeri che iniziano con 11 e non numeri da 11 a 20.
Gabriel McAdams

sebbene, se si lavora con un Enum, perché non qualcosa del genere? if (Enumerable.Range (MyEnum.A, MyEnum.M) {DoThing ();} else if (Enumerable.Range (MyEnum.N, MyEnum.Z) {DoAnotherThing ();}
David Hollowell - MSFT

4
Si noti che Enumerable.Range(11,20).Contains(c)equivale afor(int i = 11; i < 21; ++i){ if (i == c) return true; } return false; Se avessi una vasta gamma richiederebbe molto tempo, mentre solo usando >e <sarebbe veloce e costante.
Jon Hanna,

Un miglioramento: avere MySwitchWithEnumerable ritorno voidè un progetto debole per questa situazione. MOTIVO: hai convertito una if-elsein una serie di istruzioni indipendenti - che nasconde l'intento, ovvero che si escludono a vicenda - solo una actionviene eseguita. Invece ritorno bool, con il corpo if (..) { action(); return true; } else return false;il sito chiamando mostra quindi l'intenzione: if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));. Questo è preferibile. Tuttavia, non è più un miglioramento significativo rispetto alla versione originale, per questo semplice caso.
ToolmakerSteve

15

Ecco la soluzione completa C # 7 ...

switch (value)
{
   case var s when new[] { 1,2,3 }.Contains(s):
      // Do something
      break;
   case var s when new[] { 4,5,6 }.Contains(s):
      // Do something
      break;
   default:
      // Do the default
      break;
}

Funziona anche con le stringhe ...

switch (mystring)
{
   case var s when new[] { "Alpha","Beta","Gamma" }.Contains(s):
      // Do something
      break;
...
}

Ciò significherebbe che allocare le matrici con ciascuna istruzione switch, giusto? Non sarebbe meglio se le avessimo come variabili costanti?
MaLiN2223,

Elegante, ma sarebbe sicuramente utile sapere se il compilatore ottimizza questo scenario in modo che ripetute invocazioni non comportino sempre il sovraccarico della costruzione dell'array; definire le matrici in anticipo è un'opzione, ma toglie gran parte dell'eleganza.
mklement0

11

Il codice seguente non funzionerà:

case 1 | 3 | 5:
// Not working do something

L'unico modo per farlo è:

case 1: case 2: case 3:
// Do something
break;

Il codice che stai cercando funziona in Visual Basic in cui puoi facilmente inserire intervalli ... nonenell'opzione switchdell'istruzione oif else blocchi conveniente, suggerirei, a un punto molto estremo, di creare .dll con Visual Basic e importare indietro al tuo progetto C #.

Nota: l'equivalente allo switch in Visual Basic è Select Case.


7

Un'altra opzione sarebbe quella di utilizzare una routine. Se i casi 1-3 eseguono tutti la stessa logica, avvolgi quella logica in una routine e chiamala per ciascun caso. So che questo in realtà non elimina le dichiarazioni del caso, ma implementa un buon stile e mantiene la manutenzione al minimo .....

[Modifica] Aggiunta implementazione alternativa per abbinare la domanda originale ... [/ Modifica]

switch (x)
{
   case 1:
      DoSomething();
      break;
   case 2:
      DoSomething();
      break;
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

alt

switch (x)
{
   case 1:
   case 2:
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

5

Un aspetto meno noto dell'interruttore in C # è che si basa sull'operatore = e poiché può essere ignorato potresti avere qualcosa del genere:


string s = foo();

switch (s) {
  case "abc": /*...*/ break;
  case "def": /*...*/ break;
}

4
questo potrebbe diventare un grosso problema in seguito per qualcun altro che cerca di leggere il codice
Andrew Harry,

5

gcc implementa un'estensione al linguaggio C per supportare intervalli sequenziali:

switch (value)
{
   case 1...3:
      //Do Something
      break;
   case 4...6:
      //Do Something
      break;
   default:
      //Do the Default
      break;
}

Modifica : ho appena notato il tag C # sulla domanda, quindi presumibilmente una risposta gcc non aiuta.


4

In C # 7 ora abbiamo Pattern Matching in modo da poter fare qualcosa del tipo:

switch (age)
{
  case 50:
    ageBlock = "the big five-oh";
    break;
  case var testAge when (new List<int>()
      { 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 }).Contains(testAge):
    ageBlock = "octogenarian";
    break;
  case var testAge when ((testAge >= 90) & (testAge <= 99)):
    ageBlock = "nonagenarian";
    break;
  case var testAge when (testAge >= 100):
    ageBlock = "centenarian";
    break;
  default:
    ageBlock = "just old";
    break;
}

3

In realtà non mi piace anche il comando GOTO, ma è in materiali ufficiali Microsoft e qui sono consentite tutte le sintassi.

Se è possibile raggiungere il punto finale dell'elenco delle istruzioni di una sezione switch, si verifica un errore di compilazione. Questa è conosciuta come la regola "nessuna caduta". L'esempio

switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
default:
   CaseOthers();
   break;
}

è valido perché nessuna sezione switch ha un punto finale raggiungibile. A differenza di C e C ++, l'esecuzione di una sezione switch non può "passare" alla sezione switch successiva, e l'esempio

switch (i) {
case 0:
   CaseZero();
case 1:
   CaseZeroOrOne();
default:
   CaseAny();
}

genera un errore in fase di compilazione. Quando l'esecuzione di una sezione switch deve essere seguita dall'esecuzione di un'altra sezione switch, è necessario utilizzare un caso goto esplicito o un'istruzione goto default:

switch (i) {
case 0:
   CaseZero();
   goto case 1;
case 1:
   CaseZeroOrOne();
   goto default;
default:
   CaseAny();
   break;
}

Sono consentite più etichette in una sezione di commutazione. L'esempio

switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
case 2:
default:
   CaseTwo();
   break;
}

Credo in questo caso particolare, il GOTO può essere utilizzato ed è in realtà l'unico modo per fallire.

Fonte: http://msdn.microsoft.com/en-us/library/aa664749%28v=vs.71%29.aspx


Si noti che in pratica, si gotopuò quasi sempre evitare (anche se non lo considero "terribile" qui - sta riempiendo un ruolo specifico, strutturato). Nel tuo esempio, poiché hai racchiuso i corpi del case in funzioni (una buona cosa), il caso 0 può diventare CaseZero(); CaseZeroOrOne(); break;. Non gotorichiesto
ToolmakerSteve

Il collegamento è interrotto a metà (reindirizzamenti, "Documentazione tecnica ritirata di Visual Studio 2003" ).
Peter Mortensen,

2

Un sacco di lavoro sembra essere stato messo nel trovare modi per ottenere una delle sintassi meno utilizzate in C # per apparire in qualche modo migliore o funzionare meglio. Personalmente trovo che raramente valga la pena usare l'istruzione switch. Consiglio vivamente di analizzare quali dati stai testando e i risultati finali che desideri.

Diciamo ad esempio che vuoi testare rapidamente i valori in un intervallo noto per vedere se sono numeri primi. Vuoi evitare che il tuo codice esegua i calcoli inutili e puoi trovare un elenco di numeri primi nell'intervallo che desideri online. È possibile utilizzare un'enorme istruzione switch per confrontare ciascun valore con numeri primi noti.

Oppure potresti semplicemente creare una mappa di array di numeri primi e ottenere risultati immediati:

    bool[] Primes = new bool[] {
        false, false, true, true, false, true, false,    
        true, false, false, false, true, false, true,
        false,false,false,true,false,true,false};
    private void button1_Click(object sender, EventArgs e) {
        int Value = Convert.ToInt32(textBox1.Text);
        if ((Value >= 0) && (Value < Primes.Length)) {
            bool IsPrime = Primes[Value];
            textBox2.Text = IsPrime.ToString();
        }
    }

Forse vuoi vedere se un carattere in una stringa è esadecimale. È possibile utilizzare un'istruzione switch ungly e piuttosto grande.

Oppure puoi utilizzare le espressioni regolari per testare il carattere o utilizzare la funzione IndexOf per cercare il carattere in una stringa di lettere esadecimali note:

        private void textBox2_TextChanged(object sender, EventArgs e) {
        try {
            textBox1.Text = ("0123456789ABCDEFGabcdefg".IndexOf(textBox2.Text[0]) >= 0).ToString();
        } catch {
        }
    }

Diciamo che vuoi fare una delle 3 diverse azioni a seconda di un valore che sarà compreso tra 1 e 24. Suggerirei di utilizzare una serie di istruzioni IF. E se questo fosse diventato troppo complesso (o i numeri fossero più grandi, come 5 diverse azioni a seconda di un valore compreso tra 1 e 90), utilizzare un enum per definire le azioni e creare una mappa di array degli enum. Il valore verrebbe quindi utilizzato per indicizzare nella mappa dell'array e ottenere l'enum dell'azione desiderata. Quindi utilizzare un piccolo set di istruzioni IF o un'istruzione switch molto semplice per elaborare il valore enum risultante.

Inoltre, la cosa bella di una mappa di array che converte un intervallo di valori in azioni è che può essere facilmente modificata dal codice. Con il codice cablato non è possibile modificare facilmente il comportamento in fase di esecuzione ma con una mappa di array è facile.


Puoi anche mappare l'espressione lambda o un delegato
Conrad Frix il

Punti buoni. Un piccolo commento: di solito trovo più facile mantenere un elenco dei valori che corrispondono a un determinato caso, piuttosto che una mappa di array. Il problema con la mappa di array è che è facile commettere un errore. Ad esempio, invece della mappa dell'array primes di true / falses, è sufficiente disporre di un elenco di numeri primi e caricarli in un HashSet per le prestazioni di ricerca. Anche se ci sono più di due casi, di solito tutti tranne un caso sono un piccolo elenco, quindi costruisci un HashSet di enum (se sparse) o una mappa di array, in codice, dagli elenchi degli altri casi.
ToolmakerSteve

1

Solo per aggiungere alla conversazione, usando .NET 4.6.2 sono stato anche in grado di fare quanto segue. Ho testato il codice e ha funzionato per me.

Puoi anche fare più istruzioni "OR", come di seguito:

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when b.Contains("text3") || b.Contains("text4") || b.Contains("text5"):
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

Puoi anche verificare se corrisponde a un valore in un array:

            string[] statuses = { "text3", "text4", "text5"};

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when statuses.Contains(value):                        
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

Questo non dipende dalla versione C #, non dalla versione .NET?
Peter Mortensen,

1

Se hai una grande quantità di stringhe (o qualsiasi altro tipo) che fanno tutte la stessa cosa, ti consiglio di usare un elenco di stringhe combinato con la proprietà string.Contains.

Quindi se hai una grande istruzione switch in questo modo:

switch (stringValue)
{
    case "cat":
    case "dog":
    case "string3":
    ...
    case "+1000 more string": // Too many string to write a case for all!
        // Do something;
    case "a lonely case"
        // Do something else;
    .
    .
    .
}

Potresti volerlo sostituire con ifun'affermazione come questa:

// Define all the similar "case" string in a List
List<string> listString = new List<string>(){ "cat", "dog", "string3", "+1000 more string"};
// Use string.Contains to find what you are looking for
if (listString.Contains(stringValue))
{
    // Do something;
}
else
{
    // Then go back to a switch statement inside the else for the remaining cases if you really need to
}

Questa scala bene per qualsiasi numero di casi di stringa.



-5

Per questo, useresti un'istruzione goto. Ad esempio:

    switch(value){
    case 1:
        goto case 3;
    case 2:
        goto case 3;
    case 3:
        DoCase123();
    //This would work too, but I'm not sure if it's slower
    case 4:
        goto case 5;
    case 5:
        goto case 6;
    case 6:
        goto case 7;
    case 7:
        DoCase4567();
    }

7
@scone goto rompe un principio fondamentale della programmazione procedurale (di cui c ++ e c # sono ancora radicati; non sono linguaggi OO puri (grazie a Dio)). La programmazione procedurale ha un flusso di logica ben definito determinato da costrutti di linguaggio e convenzioni di metodo (come lo stack di runtime cresce e si restringe). L'affermazione goto elude questo flusso consentendo fondamentalmente il salto arbitrario.
samis,

1
Non sto dicendo che è un buon stile, credo, ma fa quello che la domanda originale stava chiedendo.
focaccina

2
No, non "fa quello che la domanda originale stava chiedendo". La domanda originale aveva un codice che funzionava così com'è . Non ne avevano bisogno riparati. E anche se lo facessero, questo è un suggerimento orribile. È meno conciso e utilizza goto. Peggio ancora, è un uso del tutto inutile goto, poiché la sintassi originale dichiarata da OP funziona. La domanda era se esistesse un modo più conciso di fornire casi alternativi. Come la gente ha risposto anni prima di te , sì, c'è - se sei disposto a mettere i vari casi su una riga case 1: case 2:e se lo stile automatico dell'editor lo consente.
ToolmakerSteve

L'unica ragione per cui i goto sono determinati a essere cattivi è perché alcune persone trovano difficile seguire il flusso logico. .Net MSIL (codice oggetto assemblato) usa goto dappertutto perché è veloce, ma se il codice .Net può essere scritto ed essere altrettanto performante senza di essi è meglio non usarli e quindi non farti infiammare da persone come @ La risposta condiscendente di ToolmakerSteve.
dynamiclynk,

@wchoward - Per favore leggi più attentamente la mia risposta. La mia lamentela non riguarda solo l'uso di goto . Ho obiettato perché la domanda mostrava un codice che funziona già così com'è , e questa risposta a) prende quel codice funzionante e lo rende più dettagliato, e meno strutturato, senza alcun vantaggio , b) non risponde alla domanda.
ToolmakerSteve
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.