Múltiples casos en la declaración de cambio

582

¿Hay alguna manera de pasar por múltiples declaraciones de casos sin indicarlo case value:repetidamente?

Sé que esto 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;
}

pero me gustaría hacer algo como esto:

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

¿Es esta sintaxis en la que estoy pensando en un idioma diferente o me falta algo?

theo
fuente
¿Hay alguna razón por la que no solo usa una instrucción IF (si está verificando un rango de entradas)?
Eric Schoonover
2
sí charlse, la primera forma funciona bien, la he usado en numerosos lugares. Está más sucio de lo que me gustaría, pero es útil. Acabo de usar esos enteros como ejemplo. Los datos reales fueron más variados. Un if (1 || 2 || 3) {...} else if (4 || 5 || 6) {...} también hubiera funcionado, pero es más difícil de leer.
theo
55
¿Por qué consideras que este último es más sucio que el primero? Este último agrega otro significado ,y uno que no se comparte con ningún otro lenguaje de estilo C. Eso me parecería mucho más sucio.
Jon Hanna
1
Es posible que hayas recogido la sintaxis del segundo de Ruby. Así es como funciona en ese idioma (aunque el cambio se convierte en mayúsculas y minúsculas, entre otras cosas)
Juanpaco
44
Nota importante . Los rangos son compatibles en el caso de cambio que comienza en C # v7 - Consulte la respuesta de
RBT

Respuestas:

313

No hay sintaxis en C ++ ni C # para el segundo método que mencionó.

No hay nada malo con tu primer método. Sin embargo, si tiene rangos muy grandes, simplemente use una serie de declaraciones if.

Brian R. Bondy
fuente
55
Como complemento, quería agregar un enlace a la especificación del lenguaje C # disponible en MSDN en msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
Richard McGuire
El usuario podría usar algunos if (o una búsqueda en la tabla) para reducir la entrada a un conjunto de enumeraciones y activar la enumeración.
Harvey
55
probablemente escogió eso de VB.net
George Birbilis
1
VB.net es mejor para varias declaraciones de casos ... excepto que no se caen como lo hace C #. Toma un poco, da un poco.
Brain2000
Creo que esto ya no es correcto. Ver stackoverflow.com/questions/20147879/… . También en esta misma pregunta hay una respuesta stackoverflow.com/a/44848705/1073157
Dan Rayson
700

Supongo que esto ya ha sido respondido. Sin embargo, creo que aún puede mezclar ambas opciones de una manera sintácticamente mejor haciendo:

switch (value)
{
    case 1: case 2: case 3:          
        // Do Something
        break;
    case 4: case 5: case 6: 
        // Do Something
        break;
    default:
        // Do Something
        break;
}
Carlos Quintanilla
fuente
3
'interruptor' debe estar en minúsculas para c #?
Austin Harris
3
El código colapsado se alarga hasta el primer ejemplo en la pregunta. También podría hacerlo como está en la pregunta.
MetalPhoenix
10
¿Por qué molestarse? El sangrado automático en Visual Studio 2013 revertirá esto al formato en la pregunta original de todos modos.
Gustav
14
@T_D está recibiendo soporte porque en realidad responde la pregunta. El OP dijo: ¿Me estoy perdiendo algo ... Carlos respondió con lo que se estaba perdiendo. Parece bastante cortado y seco para mí. No odies que tenga 422 votos a favor.
Mike Devenney
8
@MikeDevenney Luego interpretaste la pregunta de manera diferente, por lo que veo, la respuesta correcta sería "no, c # no tiene ninguna sintaxis para eso". Si alguien pregunta "¿es posible verter líquido en un vaso que estoy sosteniendo boca abajo?" la respuesta debería ser "no" y no "puedes verter líquido si lo miras al revés y usas tu imaginación", porque esta respuesta se trata de usar la imaginación. Si usa la sintaxis regular pero la formatea mal, se parece a otra sintaxis, con algo de imaginación. Espero que
entiendas
74

Esta sintaxis es de Visual Basic Select ... Declaración de caso :

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

No puede usar esta sintaxis en C #. En su lugar, debe usar la sintaxis de su primer ejemplo.

Neal
fuente
49
Esta es una de las pocas cosas que extraño de * Basic.
nickf
10
Aquí tenemos una de las pocas pantallas donde visual basic no es tan feo y es más versátil que c #. Este es un valioso ejemplo!
bgmCoder
Esto es bastante decente. Me pregunto por qué esto no se ha agregado con C # 8.0. Sería muy lindo
Sigex
69

En C # 7 (disponible de forma predeterminada en Visual Studio 2017 / .NET Framework 4.6.2), el cambio basado en rango ahora es posible con la declaración de cambio y ayudaría con el problema del OP.

Ejemplo:

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:

  • Los paréntesis (y )no son obligatorios en la whencondición, pero se usan en este ejemplo para resaltar las comparaciones.
  • vartambién se puede usar en lugar de int. Por ejemplo: case var n when n >= 7:.
Steve Gomez
fuente
3
Esta (coincidencia de patrones) generalmente debería ser una mejor práctica cuando puede usar C # 7.xo superior, ya que es mucho más claro que las otras respuestas.
UndyingJellyfish
¿Hay alguna manera de lograr esto con una lista de Enums? ¿Dónde se asignan las enumeraciones a int?
Sigex
33

Puede omitir la nueva línea que le brinda:

case 1: case 2: case 3:
   break;

pero considero ese mal estilo.

Allan Wind
fuente
20

.NET Framework 3.5 tiene rangos:

Enumerable Rango desde MSDN

puede usarlo con "contiene" y la instrucción IF, ya que, como alguien dijo, la instrucción SWITCH utiliza el operador "==".

Aquí un ejemplo:

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

Pero creo que podemos divertirnos más: como no necesitará los valores de retorno y esta acción no toma parámetros, ¡puede usar acciones fácilmente!

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

El viejo ejemplo con este nuevo método:

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

Como estás pasando acciones, no valores, debes omitir el paréntesis, es muy importante. Si necesita una función con argumentos, simplemente cambie el tipo de Actiona Action<ParameterType>. Si necesita valores de retorno, use Func<ParameterType, ReturnType>.

En C # 3.0 no hay una aplicación parcial fácil para encapsular el hecho de que el parámetro case es el mismo, pero se crea un pequeño método auxiliar (un poco detallado).

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

Aquí un ejemplo de cómo las nuevas declaraciones funcionales importadas son, en mi humilde opinión, más poderosas y elegantes que la antigua imperativa.

Luca Molteni
fuente
3
Buena elección. Una cosa a tener en cuenta, sin embargo: Enumerable.Range tiene argumentos int starty int count. Sus ejemplos no funcionarán correctamente tal como fueron escritos. Lo escribes como si el segundo argumento es int end. Por ejemplo, Enumerable.Range(11,20)daría como resultado 20 números que comienzan con 11, y no números del 11 al 20.
Gabriel McAdams
aunque, si trabajas con un Enum, ¿por qué no algo así? if (Enumerable.Range (MyEnum.A, MyEnum.M) {DoThing ();} else if (Enumerable.Range (MyEnum.N, MyEnum.Z) {DoAnotherThing ();}
David Hollowell - MSFT
44
Tenga en cuenta que Enumerable.Range(11,20).Contains(c)es equivalente a for(int i = 11; i < 21; ++i){ if (i == c) return true; } return false;Si tuviera un rango grande, tomaría mucho tiempo, mientras lo usa >y <sería rápido y de tiempo constante.
Jon Hanna
Una mejora: Tener MySwitchWithEnumerableretorno voides un diseño débil para esta situación. RAZÓN: Has convertido if-elseuna serie de declaraciones independientes, que oculta la intención, que es que son mutuamente excluyentes, solo actionse ejecuta una. En vez de regreso bool, con el cuerpo if (..) { action(); return true; } else return false;El sitio de llamar a continuación, muestra la intención: if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));. Esto es preferible. Sin embargo, ya no es una mejora significativa sobre su versión original, para este caso simple.
ToolmakerSteve
15

Aquí está la solución completa de 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;
}

Funciona con cadenas también ...

switch (mystring)
{
   case var s when new[] { "Alpha","Beta","Gamma" }.Contains(s):
      // Do something
      break;
...
}
Carter Medlin
fuente
Esto significaría que asigna las matrices con cada instrucción de cambio, ¿verdad? ¿No sería mejor si las tuviéramos como variables constantes?
MaLiN2223
Elegante, pero sería bueno saber si el compilador optimiza este escenario para que las invocaciones repetidas no incurran en la sobrecarga de la construcción del arreglo todo el tiempo; definir las matrices con anticipación es una opción, pero quita gran parte de la elegancia.
mklement0
11

El siguiente código no funcionará:

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

La única forma de hacer esto es:

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

El código que está buscando funciona en Visual Basic, donde puede colocar fácilmente rangos ... en la noneopción de la switchinstrucción oif else bloques convenientes, sugeriría, en un punto muy extremo, hacer .dll con Visual Basic e importar de nuevo a su proyecto C #.

Nota: el equivalente de conmutador en Visual Basic es Select Case.

ninguna
fuente
7

Otra opción sería usar una rutina. Si todos los casos 1-3 ejecutan la misma lógica, envuelva esa lógica en una rutina y llámela para cada caso. Sé que esto realmente no elimina las declaraciones de casos, pero implementa un buen estilo y mantiene el mantenimiento al mínimo .....

[Editar] Se agregó una implementación alternativa para que coincida con la pregunta 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()
{
   ...
}
Dr8k
fuente
5

Una faceta menos conocida del interruptor en C # es que se basa en el operador = y dado que se puede anular, podría tener algo como esto:


string s = foo();

switch (s) {
  case "abc": /*...*/ break;
  case "def": /*...*/ break;
}
Cyber ​​Oliveira
fuente
44
Esto podría convertirse en un gran problema más adelante para alguien que intente leer el código
Andrew Harry
5

gcc implementa una extensión del lenguaje C para admitir rangos secuenciales:

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

Editar : Acabo de notar la etiqueta C # en la pregunta, por lo que presumiblemente una respuesta gcc no ayuda.

DGentry
fuente
4

En C # 7 ahora tenemos Pattern Matching para que pueda hacer 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;
}
Luke T O'Brien
fuente
3

En realidad, tampoco me gusta el comando GOTO, pero está en los materiales oficiales de Microsoft, y aquí están todas las sintaxis permitidas.

Si se puede alcanzar el punto final de la lista de instrucciones de una sección de conmutador, se produce un error en tiempo de compilación. Esto se conoce como la regla de "no caerse". El ejemplo

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

es válido porque ninguna sección del interruptor tiene un punto final alcanzable. A diferencia de C y C ++, la ejecución de una sección de cambio no está permitida para "pasar por alto" a la siguiente sección de cambio, y el ejemplo

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

da como resultado un error en tiempo de compilación. Cuando la ejecución de una sección de cambio va a ser seguida por la ejecución de otra sección de cambio, se debe usar un caso de goto explícito o una declaración predeterminada de goto:

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

Se permiten múltiples etiquetas en una sección de interruptor. El ejemplo

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

Creo que en este caso particular, se puede usar GOTO, y en realidad es la única forma de fracasar.

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

Jiří Herník
fuente
Tenga en cuenta que en la práctica, gotocasi siempre se puede evitar (aunque no lo considero "terrible" aquí, está cumpliendo un rol específico y estructurado). En su ejemplo, debido a que ha envuelto los cuerpos de los casos en funciones (algo bueno), el caso 0 puede convertirse CaseZero(); CaseZeroOrOne(); break;. No se gotorequiere
ToolmakerSteve
El enlace está medio roto (redirecciona, "Documentación técnica retirada de Visual Studio 2003" ).
Peter Mortensen
2

Parece que se ha realizado una gran cantidad de trabajo para encontrar formas de obtener una de las sintaxis menos utilizadas de C # para que parezca mejor o funcione mejor. Personalmente, creo que la declaración de cambio rara vez vale la pena usarla. Sugeriría encarecidamente analizar qué datos está probando y los resultados finales que desea.

Digamos, por ejemplo, que desea probar rápidamente los valores en un rango conocido para ver si son números primos. Desea evitar que su código haga los cálculos innecesarios y puede encontrar una lista de números primos en el rango que desea en línea. Podría usar una declaración de cambio masivo para comparar cada valor con números primos conocidos.

O simplemente podría crear un mapa de matriz de números primos y obtener resultados inmediatos:

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

Tal vez quiera ver si un carácter en una cadena es hexadecimal. Podría usar una declaración de cambio fea y algo grande.

O puede usar expresiones regulares para probar el carácter o utilizar la función IndexOf para buscar el carácter en una cadena de letras hexadecimales conocidas:

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

Digamos que desea realizar una de 3 acciones diferentes dependiendo de un valor que será el rango de 1 a 24. Sugeriría usar un conjunto de declaraciones IF. Y si eso se volvió demasiado complejo (o los números eran más grandes, como 5 acciones diferentes dependiendo de un valor en el rango de 1 a 90), use una enumeración para definir las acciones y crear un mapa de matriz de las enumeraciones. El valor se usaría para indexar en el mapa de matriz y obtener la enumeración de la acción que desea. Luego use un pequeño conjunto de declaraciones IF o una declaración de cambio muy simple para procesar el valor de enumeración resultante.

Además, lo bueno de un mapa de matriz que convierte un rango de valores en acciones es que se puede cambiar fácilmente por código. Con el código cableado no puede cambiar fácilmente el comportamiento en tiempo de ejecución, pero con un mapa de matriz es fácil.

Darin
fuente
También puede asignar a la expresión lambda o un delegado
Conrad Frix
Buenos puntos. Un comentario menor: generalmente me resulta más fácil mantener una lista de los valores que coinciden con un caso determinado, que un mapa de matriz. El problema con el mapa de matriz es que es fácil cometer un error. Por ejemplo, en lugar del mapa de matriz de primos de verdadero / falso, simplemente tenga una lista de primos y cárguelos en un HashSet para el rendimiento de búsqueda. Incluso si hay más de dos casos, generalmente todos menos uno son una lista pequeña, por lo tanto, cree un HashSet de enumeraciones (si es escaso) o un mapa de matriz, en código, a partir de las listas de los otros casos.
ToolmakerSteve
1

Solo para agregar a la conversación, usando .NET 4.6.2 También pude hacer lo siguiente. Probé el código y funcionó para mí.

También puede hacer varias declaraciones "OR", como a continuación:

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

También puede verificar si coincide con un valor en una 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;
            }
JeffS
fuente
¿No depende esto de la versión C #, no de la versión .NET?
Peter Mortensen
1

Si tiene una gran cantidad de cadenas (o de cualquier otro tipo) en el caso de que todas hagan lo mismo, recomiendo el uso de una lista de cadenas combinada con la cadena. Contiene la propiedad.

Entonces, si tiene una declaración de cambio grande como esta:

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

Es posible que desee reemplazarlo con una ifdeclaración 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
}

Esto escala bien para cualquier número de casos de cadena.

Maxter
fuente
-5

Para esto, usarías una declaración goto. 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();
    }
bollo
fuente
77
@scone goto rompe un principio fundamental de la programación de procedimientos (de los cuales c ++ y c # todavía están arraigados; no son lenguajes OO puros (gracias a Dios)). La programación de procedimientos tiene un flujo lógico bien definido determinado por construcciones de lenguaje y convenciones de llamada a métodos (cómo la pila de tiempo de ejecución crece y se reduce). La declaración goto evita este flujo al permitir saltos arbitrarios, básicamente.
samis
1
No digo que sea un buen estilo, según mi opinión, pero hace lo que pedía la pregunta original.
Scone
2
No, no "hace lo que pedía la pregunta original". La pregunta original tenía un código que funcionaba como está . No lo necesitaban arreglado. E incluso si lo hicieran, esta es una sugerencia horrible. Es menos conciso, y utiliza goto. Peor aún, es un uso completamente innecesario goto, ya que la sintaxis original establecida por OP funciona. La pregunta era si había una forma más concisa de dar los casos alternativos. Como la gente respondió años antes que usted , sí, si está dispuesto a poner varios casos en una línea case 1: case 2:, y si el estilo automático del editor lo permite.
ToolmakerSteve
La única razón por la que se determina que los goto son malos es porque a algunas personas les resulta difícil seguir el flujo lógico. .Net MSIL (código de objeto ensamblado) usa goto en todas partes porque es rápido, pero si el código .Net puede escribirse y ser igual de eficaz sin ellos, es mejor no usarlos y así no te critiquen personas como @ La respuesta condescendiente de ToolmakerSteve.
dynamiclynk
@wchoward - Lea mi respuesta con más cuidado. Mi queja no es solo sobre el uso de goto . Me opuse porque la pregunta mostraba un código que ya funciona como está , y esta respuesta a) toma ese código de trabajo y lo hace más detallado y menos estructurado, sin ningún beneficio , b) no responde la pregunta.
ToolmakerSteve