Vários casos na instrução switch


582

Existe uma maneira de passar por várias instruções de caso sem declarar case value:repetidamente?

Eu sei que isso funciona:

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

mas eu gostaria de fazer algo assim:

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

É esta sintaxe que estou pensando em um idioma diferente ou estou perdendo alguma coisa?


Existe uma razão para você não usar apenas uma instrução IF (se estiver verificando uma variedade de entradas)?
Eric Schoonover

2
sim charlse, a primeira maneira funciona bem, eu usei em vários lugares. É mais sujo do que eu gostaria, mas é útil. Eu apenas usei esses números inteiros como exemplo. Os dados reais foram mais variados. An if (1 || 2 || 3) {...} else if (4 || 5 || 6) {...} também teria funcionado, mas é mais difícil de ler.
theo

5
por que você considera o último mais sujo que o anterior? O último adiciona ainda outro significado ,e um que não é compartilhado com nenhuma outra linguagem c-style. Isso me pareceria muito mais sujo.
Jon Hanna

1
Você pode ter escolhido a sintaxe do 2º do Ruby. É assim que funciona nesse idioma (embora interruptor se torna caso, e caso se torna quando, entre outras coisas.)
juanpaco

4
Nota importante . As faixas são suportadas no caso de switch iniciando em C # v7 - Veja a resposta de
RBT

Respostas:


313

Não há sintaxe em C ++ nem C # para o segundo método que você mencionou.

Não há nada errado com o seu primeiro método. Se, no entanto, você tiver intervalos muito grandes, basta usar uma série de instruções if.


5
Como um complemento eu queria adicionar um link para a especificação da linguagem C # disponível no MSDN em msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
Richard McGuire

O usuário pode usar alguns if's (ou uma pesquisa de tabela) para reduzir a entrada para um conjunto de enumerações e ativar a enumeração.
Harvey

5
provavelmente escolheu isso em VB.net
George Birbilis

1
O VB.net é melhor para várias declarações de casos ... exceto que elas não aparecem como o C #. Tome um pouco, dê um pouco.
precisa saber é o seguinte

Eu acredito que isso não está mais correto. Consulte stackoverflow.com/questions/20147879/… . Também nesta questão há uma resposta stackoverflow.com/a/44848705/1073157
Dan Rayson

700

Eu acho que isso já foi respondido. No entanto, acho que você ainda pode misturar as duas opções de uma maneira sintaticamente melhor fazendo:

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' deve estar em minúsculas para c #?
Austin Harris

3
O código recolhido é ampliado para o primeiro exemplo da pergunta. Também pode fazê-lo da maneira que está na questão.
MetalPhoenix

10
Porque se importar? O indentador automático no Visual Studio 2013 reverterá isso para o formato na pergunta original de qualquer maneira.
Gustav

14
O @T_D está recebendo suporte porque realmente responde à pergunta. O OP disse: estou perdendo alguma coisa ... Carlos respondeu com o que estava faltando. Parece bem cortado e seco para mim. Não odeie que ele tenha 422 votos positivos.
precisa

8
@ MikeDevenney Então você interpretou a pergunta de maneira diferente, até onde eu vejo a resposta correta seria "não, c # não tem nenhuma sintaxe para isso". Se alguém perguntar "é possível derramar líquido em um copo que estou segurando de cabeça para baixo?" a resposta deve ser "não" e não "você pode derramar líquido se olhar de cabeça para baixo e usar a imaginação", porque essa resposta é sobre como usar a imaginação. Se você usar a sintaxe regular, mas formatá-la mal, ela se parecerá com outra sintaxe, com alguma imaginação. Espero que você entenda meu ponto ...: P
T_D 31/03/16

74

Essa sintaxe é da instrução de caso Visual Basic Select ... :

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

Você não pode usar esta sintaxe em c #. Em vez disso, você deve usar a sintaxe do seu primeiro exemplo.


49
essa é uma das poucas coisas que sinto falta * do Basic.
nickf

10
Aqui temos uma das poucas telas em que o visual basic não é tão feio e é mais versátil que o c #. Este é um exemplo valioso!
precisa saber é o seguinte

Isso é bastante decente. Gostaria de saber por que isso não foi adicionado ao C # 8.0. Seria muito bom.
Sigex 12/12

69

No C # 7 (disponível por padrão no Visual Studio 2017 / .NET Framework 4.6.2), a comutação baseada em intervalo agora é possível com a instrução switch e ajudaria no problema do OP.

Exemplo:

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

Notas:

  • Os parênteses (e )não são necessários na whencondição, mas são usados ​​neste exemplo para destacar as comparações.
  • vartambém pode ser usado no lugar de int. Por exemplo: case var n when n >= 7:.

3
Essa (correspondência de padrão) geralmente deve ser uma prática recomendada quando você pode usar o C # 7.x ou superior, pois é muito mais claro que as outras respostas.
UndyingJellyfish

Existe uma maneira de conseguir isso com uma lista de enums? Para onde o Enums mapeia para int?
Sigex 12/12

33

Você pode deixar de fora a nova linha que fornece:

case 1: case 2: case 3:
   break;

mas considero esse estilo ruim.


20

O .NET Framework 3.5 possui intervalos:

Enumerable.Range do MSDN

você pode usá-lo com "contains" e a instrução IF, pois, como alguém disse, a instrução SWITCH usa o operador "==".

Aqui está um exemplo:

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

Mas acho que podemos nos divertir mais: como você não precisará dos valores de retorno e essa ação não aceita parâmetros, você pode facilmente usar ações!

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

O exemplo antigo com este novo método:

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

Como você está passando ações, não valores, você deve omitir os parênteses, é muito importante. Se você precisar de função com argumentos, basta alterar o tipo de Actionpara Action<ParameterType>. Se você precisar de valores de retorno, use Func<ParameterType, ReturnType>.

No C # 3.0, não existe um Aplicativo Parcial fácil para encapsular o fato de que o parâmetro case é o mesmo, mas você cria um pequeno método auxiliar (um pouco detalhado, em inglês).

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

Aqui está um exemplo de como a nova declaração importada funcional é IMHO mais poderosa e elegante do que a antiga e imperativa.


3
Boa escolha. Porém, uma coisa a ser observada - Enumerable.Range possui argumentos int starte int count. Seus exemplos não funcionam da maneira como foram escritos. Você escreve como se o segundo argumento fosse int end. Por exemplo - Enumerable.Range(11,20)resultaria em 20 números que começam com 11, e não números de 11 a 20.
Gabriel McAdams

embora, se estiver trabalhando com um Enum, por que não algo assim? if (Enumerable.Range (MyEnum.A, MyEnum.M) {DoThing ();} else if (Enumerable.Range (MyEnum.N, MyEnum.Z) {DoAnotherThing ();)
David Hollowell - MSFT

4
Observe que isso Enumerable.Range(11,20).Contains(c)é equivalente a for(int i = 11; i < 21; ++i){ if (i == c) return true; } return false;Se você tivesse um intervalo grande, levaria muito tempo, enquanto apenas o usasse >e <seria rápido e constante.
31916 Jon Hanna

Uma melhoria: ter MySwitchWithEnumerableretorno voidé um design fraco para esta situação. MOTIVO: Você converteu um if-elseem uma série de instruções independentes - que ocultam a intenção, que é que elas sejam mutuamente exclusivas - apenas uma actioné executada. Em vez voltar bool, com o corpo if (..) { action(); return true; } else return false;O site chamando, em seguida, mostra a intenção: if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));. Isto é preferível. No entanto, também não é mais uma melhoria significativa em relação à sua versão original, neste caso simples.
Home

15

Aqui está a solução completa em 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;
}

Também funciona com cordas ...

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

Isso significaria que você alocasse as matrizes com cada instrução do switch, certo? Não seria melhor se as tivéssemos como variáveis ​​constantes?
MaLiN2223

Elegante, mas seria bom saber se o compilador otimiza esse cenário para que invocações repetidas não ocorram sempre a sobrecarga da construção da matriz; definir as matrizes antes do tempo é uma opção, mas tira grande parte da elegância.
mklement0

11

O código abaixo não funciona:

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

A única maneira de fazer isso é:

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

O código que você procura funciona no Visual Basic, onde você pode facilmente colocar intervalos ... na noneopção da switchinstrução ou nos if elseblocos convenientes, sugiro, em um ponto extremo, criar .dll com o Visual Basic e importar de volta para o seu projeto c #.

Nota: o equivalente do comutador no Visual Basic é Select Case .


7

Outra opção seria usar uma rotina. Se todos os casos 1 a 3 executarem a mesma lógica, envolva essa lógica em uma rotina e chame-a para cada caso. Sei que isso não se livra das declarações de caso, mas implementa um bom estilo e mantém a manutenção no mínimo ...

[Editar] Adicionada implementação alternativa para corresponder à pergunta original ... [/ Editar]

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

Uma faceta menos conhecida do switch em C # é que ele depende do operador = e, como pode ser substituído, você pode ter algo assim:


string s = foo();

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

4
isso poderia se tornar um grande pegadinha mais tarde para alguém tentando ler o código
Andrew Harry

5

O gcc implementa uma extensão para a linguagem C para suportar intervalos seqüenciais:

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

Edit : Acabei de notar a tag C # na pergunta, então presumivelmente uma resposta do GCC não ajuda.


4

No C # 7, agora temos a correspondência de padrões para que você possa fazer algo como:

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

Na verdade, eu também não gosto do comando GOTO, mas está nos materiais oficiais da Microsoft e aqui estão todas as sintaxes permitidas.

Se o ponto final da lista de instruções de uma seção de opção estiver acessível, ocorrerá um erro em tempo de compilação. Isso é conhecido como regra "sem falhas". O exemplo

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

é válido porque nenhuma seção de chave possui um ponto final acessível. Diferentemente de C e C ++, a execução de uma seção de chave não é permitida "cair" para a próxima seção de chave e o exemplo

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

resulta em um erro em tempo de compilação. Quando a execução de uma seção de chave deve ser seguida pela execução de outra seção de chave, é necessário usar um caso explícito goto case ou goto default:

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

Múltiplas etiquetas são permitidas em uma seção de chave. O exemplo

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

Acredito que neste caso em particular, o GOTO pode ser usado, e é realmente a única maneira de avançar.

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


Observe que, na prática, o gotoquase sempre pode ser evitado (embora eu não o considere "terrível" aqui - ele está desempenhando um papel específico e estruturado). No seu exemplo, como você envolveu os corpos do caso em funções (uma coisa boa), o caso 0 pode se tornar CaseZero(); CaseZeroOrOne(); break;. Não é gotonecessário.
Home

O link está meio quebrado (redireciona, "Documentação técnica aposentada do Visual Studio 2003" ).
Peter Mortensen

2

Parece que muito trabalho foi feito para encontrar maneiras de fazer com que uma das sintaxes menos usadas do C # parecesse melhor ou funcionasse melhor. Pessoalmente, acho que raramente vale a pena usar a instrução switch. Eu sugiro fortemente analisar quais dados você está testando e os resultados finais que deseja.

Digamos, por exemplo, que você deseja testar rapidamente valores em um intervalo conhecido para ver se são números primos. Você deseja evitar que seu código faça cálculos desnecessários e pode encontrar uma lista de números primos no intervalo que deseja online. Você pode usar uma instrução de comutação massiva para comparar cada valor com números primos conhecidos.

Ou você pode simplesmente criar um mapa de matriz de números primos e obter resultados imediatos:

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

Talvez você queira ver se um caractere em uma string é hexadecimal. Você pode usar uma declaração de chave feia e um tanto grande.

Ou você pode usar expressões regulares para testar o char ou usar a função IndexOf para procurar o char em uma sequência de letras hexadecimais conhecidas:

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

Digamos que você queira executar uma das três ações diferentes, dependendo de um valor que esteja no intervalo de 1 a 24. Eu sugeriria o uso de um conjunto de instruções IF. E se isso se tornar muito complexo (ou os números forem maiores, como 5 ações diferentes, dependendo de um valor no intervalo de 1 a 90), use uma enumeração para definir as ações e criar um mapa de matriz das enumerações. O valor seria então usado para indexar no mapa da matriz e obter a enumeração da ação que você deseja. Em seguida, use um pequeno conjunto de instruções IF ou uma instrução switch muito simples para processar o valor de enumeração resultante.

Além disso, o bom de um mapa de matriz que converte um intervalo de valores em ações é que ele pode ser facilmente alterado por código. Com código com fio, você não pode alterar facilmente o comportamento em tempo de execução, mas com um mapa de matriz é fácil.


Você também pode mapear a expressão lambda ou um delegado
Conrad Frix

Bons pontos. Um pequeno comentário: geralmente acho mais fácil manter uma lista dos valores que correspondem a um determinado caso do que um mapa de matriz. O problema com o mapa de matriz é que é fácil cometer um erro. Por exemplo, em vez do mapa da matriz de números primos de verdadeiro / falso, basta ter uma lista de números primos e carregá-los em um HashSet para obter desempenho de pesquisa. Mesmo se houver mais de dois casos, geralmente todos, exceto um, são uma lista pequena; portanto, crie um HashSet de enumerações (se esparsas) ou um mapa de matriz, no código, a partir das listas dos outros casos.
ToolmakerSteve

1

Apenas para adicionar à conversa, usando o .NET 4.6.2, também pude fazer o seguinte. Testei o código e funcionou para mim.

Você também pode fazer várias instruções "OU", como abaixo:

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

Você também pode verificar se ele corresponde a um valor em uma matriz:

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

Isso não depende da versão C #, não da versão .NET?
Peter Mortensen

1

Se você tiver uma quantidade muito grande de seqüências de caracteres (ou qualquer outro tipo) fazendo a mesma coisa, recomendo o uso de uma lista de seqüências combinada com a propriedade string.Contains.

Então, se você tem uma declaração de switch grande assim:

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

Você pode substituí-lo por uma ifdeclaração como esta:

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

Essa escala é boa para qualquer número de casos de cadeia.



-5

Para isso, você usaria uma instrução goto. Tal como:

    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
O @scone goto quebra um princípio fundamental da programação procedural (da qual c ++ e c # ainda estão enraizados; eles não são linguagens OO puras (graças a Deus)). A programação procedural possui um fluxo de lógica bem definido, determinado por construções de linguagem e convenções de chamada de método (como a pilha de tempo de execução cresce e diminui). A declaração goto contorna esse fluxo, permitindo saltar arbitrariamente, basicamente.
samis 4/12/12

1
Não estou dizendo que é bom estilo, persay, mas faz o que a pergunta original estava pedindo.
scone

2
Não, não "faz o que a pergunta original estava pedindo". A pergunta original tinha um código que funcionava como está . Eles não precisaram consertar. E mesmo se o fizessem, essa é uma sugestão horrível. É menos conciso e usa goto. Pior, é um uso completamente desnecessário goto, pois a sintaxe original declarada pelo OP funciona. A questão era se havia uma maneira mais concisa de dar os casos alternativos. Como as pessoas responderam anos antes de você , sim, existe - se você deseja colocar os vários casos em uma linha case 1: case 2:e se o estilo automático do editor permitir.
ToolmakerSteve

A única razão pela qual Goto é determinado como ruim é porque algumas pessoas acham difícil seguir o fluxo lógico. O .net MSIL (código de objeto montado) usa goto em todo o lado porque é rápido, mas se o código .Net puder ser escrito e ter o mesmo desempenho sem eles, é melhor não usá-los e assim você não será inflamado por pessoas como @ Resposta condescendente de ToolmakerSteve.
dynamiclynk

@ wchoward - Por favor, leia minha resposta com mais cuidado. Minha reclamação não é apenas sobre o uso do goto . Eu opus porque a pergunta mostrava um código que já funciona como está , e essa resposta a) pega esse código de trabalho e o torna mais detalhado e menos bem estruturado, sem benefício , b) não responde à pergunta.
ToolmakerSteve
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.