¿Cambiar la sentencia fallthrough en C#?


Switch statement fallthrough es una de mis principales razones personales para amar switch vs. if/else if construcciones. Un ejemplo está en orden aquí:

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

Las personas inteligentes se están encogiendo porque las string[]s deben declararse fuera de la función: bueno, lo son, esto es solo un ejemplo.

El compilador falla con el siguiente error:

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

¿Por qué? ¿Y hay alguna manera de obtener este tipo de comportamiento sin tener tres ifs?

Author: Matthew Scharley, 2008-10-06

14 answers

(Copiar / pegar una respuesta que proporcioné en otro lugar )

Falling through switch-cases se puede lograr sin tener código en un case (ver case 0), o usando las formas especiales goto case (ver case 1) o goto default (ver case 2):

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;
}
 577
Author: Alex Lyman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:36

El "por qué" es evitar caídas accidentales, por lo que estoy agradecido. Esta es una fuente no infrecuente de errores en C y Java.

La solución es usar goto, por ejemplo,

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

El diseño general de switch/case es un poco desafortunado en mi opinión. Se quedó demasiado cerca de C-hay algunos cambios útiles que podrían hacerse en términos de alcance, etc. Podría decirse que un interruptor más inteligente que podría hacer coincidencia de patrones, etc sería útil, pero eso realmente está cambiando de interruptor a "comprobar una secuencia de condiciones" - en cuyo punto tal vez se llamaría a un nombre diferente.

 36
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-11 21:24:52

Switch fallthrough es históricamente una de las principales fuentes de errores en los softwares modernos. El diseñador de lenguaje decidió hacer obligatorio saltar al final del caso, a menos que esté incumpliendo con el siguiente caso directamente sin procesamiento.

switch(value)
{
    case 1:// this is still legal
    case 2:
}
 26
Author: Coincoin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 13:06:10

Para agregar a las respuestas aquí, creo que vale la pena considerar la pregunta opuesta junto con esto, a saber. ¿por qué C permitió la caída en primer lugar?

Cualquier lenguaje de programación por supuesto sirve para dos objetivos: {[28]]}

  1. Proporcionar instrucciones a la computadora.
  2. Deje un registro de las intenciones del programador.

La creación de cualquier lenguaje de programación es por lo tanto un equilibrio entre cómo servir mejor a estos dos objetivos. Por un lado a mano, cuanto más fácil sea convertir en instrucciones de computadora (ya sean código máquina, bytecode como IL, o las instrucciones se interpretan en la ejecución), más capaz será ese proceso de compilación o interpretación para ser eficiente, confiable y compacto en la salida. Llevado a su extremo, este objetivo resulta en que solo escribamos en assembly, IL, o incluso en raw op-codes, porque la compilación más fácil es donde no hay compilación en absoluto.

Inversamente, cuanto más el lenguaje expresa la intención del programador, en lugar de los medios utilizados para ese fin, más comprensible el programa tanto al escribir como durante el mantenimiento.

Ahora, switch siempre podría haberse compilado convirtiéndolo en la cadena equivalente de if-else bloques o similar, pero fue diseñado para permitir la compilación en un patrón de ensamblaje común particular donde uno toma un valor, calcula un desplazamiento de él (ya sea buscando una tabla indexada por un hash perfecto del valor, o por aritmética real sobre el valor*). Vale la pena señalar en este punto que hoy en día, la compilación en C # a veces se convertirá switch en el equivalente if-else, y a veces utilizará un enfoque de salto basado en hash (y del mismo modo con C, C++ y otros lenguajes con sintaxis comparable).

En este caso hay dos buenas razones para permitir la caída:{[28]]}

  1. Simplemente sucede naturalmente de todos modos: si construye una tabla de salto en un conjunto de instrucciones, y uno de los lotes anteriores de las instrucciones no contienen algún tipo de salto o retorno, entonces la ejecución progresará naturalmente en el siguiente lote. Permitir que fall-through era lo que "simplemente sucedería" si se convierte el switch-usando C en jump-table–usando código máquina.

  2. Los codificadores que escribieron en assembly ya estaban acostumbrados al equivalente: al escribir una tabla de salto a mano en assembly, tendrían que considerar si un bloque de código determinado terminaría con un retorno, un salto fuera de la tabla, o simplemente continúe hasta la siguiente cuadra. Como tal, hacer que el codificador agregue un break explícito cuando sea necesario era "natural" para el codificador también.

En ese momento, por lo tanto, era un intento razonable de equilibrar los dos objetivos de un lenguaje informático en lo que se refiere tanto al código máquina producido, y la expresividad del código fuente.

Cuatro décadas después, sin embargo, las cosas no son exactamente las mismas, por algunas razones: {[28]]}

  1. Los codificadores en C hoy pueden tener poco o nada experiencia en montaje. Los codificadores en muchos otros lenguajes de estilo C son aún menos propensos a (especialmente Javascript!). Cualquier concepto de "a lo que la gente está acostumbrada desde la asamblea" ya no es relevante.
  2. Las mejoras en las optimizaciones significan que la probabilidad de que switch se convierta en if-else porque se consideró que el enfoque probablemente sería más eficiente, o bien se convirtió en una variante particularmente esotérica del enfoque de tabla de salto es mayor. El mapeo entre el nivel superior y el nivel inferior los enfoques no son tan fuertes como antes.
  3. La experiencia ha demostrado que la caída tiende a ser el caso minoritario en lugar de la norma (un estudio del compilador de Sun encontró que el 3% de los bloques switch usaban una caída que no fuera múltiples etiquetas en el mismo bloque, y se pensó que el caso de uso aquí significaba que este 3% era de hecho mucho más alto de lo normal). Así que el lenguaje estudiado hace que lo inusual sea más fácilmente atendido que lo común.
  4. La experiencia ha demostrado que tiende a ser la fuente de problemas, tanto en los casos en que se hace accidentalmente, y también en los casos en que la caída correcta se pierde por alguien que mantiene el código. Esto último es una adición sutil a los errores asociados con fall-through, porque incluso si su código está perfectamente libre de errores, su fall-through todavía puede causar problemas.

En relación con estos dos últimos puntos, considere la siguiente cita de la edición actual de K & R: {[28]]}

Cayendo a través de uno case to another no es robusto, siendo propenso a la desintegración cuando se modifica el programa. Con la excepción de múltiples etiquetas para un solo cálculo, las fallas deben ser usadas con moderación y comentadas.

Como cuestión de buena forma, ponga un descanso después del último caso (el predeterminado aquí) aunque sea lógicamente innecesario. Algún día cuando se añada otro caso al final, este poco de programación defensiva te salvará.

Entonces, de la boca del caballo, la caída en C es problemática. Se considera una buena práctica documentar siempre las fallas con comentarios, que es una aplicación del principio general de que uno debe documentar cuando hace algo inusual, porque eso es lo que tropezará con el examen posterior del código y/o hará que su código parezca que tiene un error de principiante cuando de hecho es correcto.

Y cuando lo pienses, codifica así: {[28]]}

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

Es añadiendo algo para hacer la fall-through explícito en el código, simplemente no es algo que pueda ser detectado (o cuya ausencia pueda ser detectada) por el compilador.

Como tal, el hecho de que on tenga que ser explícito con fall-through en C# no agrega ninguna penalización a las personas que escribieron bien en otros lenguajes de estilo C de todos modos, ya que ya serían explícitos en sus fall-through.†

Finalmente, el uso de goto aquí ya es una norma de C y otros lenguajes similares:

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

En este tipo de casos donde queremos que se incluya un bloque en el código ejecutado por un valor distinto al que lleva uno al bloque anterior, entonces ya tenemos que usar goto. (Por supuesto, hay medios y maneras de evitar esto con diferentes condicionales, pero eso es cierto de casi todo lo relacionado con esta pregunta). Como tal, C# se basa en la forma ya normal de lidiar con una situación en la que queremos golpear más de un bloque de código en un switch, y simplemente lo generalizó para cubrir caída a través también. También hizo que ambos casos fueran más convenientes y se auto-documentaran, ya que tenemos que agregar una nueva etiqueta en C pero podemos usar el case como una etiqueta en C#. En C# podemos deshacernos de la etiqueta below_six y usar goto case 5 que es más claro en cuanto a lo que estamos haciendo. (También tendríamos que agregar break para el default, que omití solo para hacer que el código C anterior claramente no sea código C#).

En resumen, por lo tanto:

  1. C # ya no se relaciona con la salida del compilador no optimizada tan directamente como C el código lo hizo hace 40 años (ni C en estos días), lo que hace que una de las inspiraciones de fall-through sea irrelevante.
  2. C# sigue siendo compatible con C en no solo tener implícito break, para facilitar el aprendizaje del lenguaje por aquellos familiarizados con lenguajes similares, y una portabilidad más fácil.
  3. C# elimina una posible fuente de errores o código mal entendido que ha sido bien documentado como causante de problemas durante las últimas cuatro décadas.
  4. C # hace que las mejores prácticas existentes con C (documento fall a través de) exigible por el compilador.
  5. C# hace que el caso inusual sea el que tiene código más explícito, el caso habitual es el que tiene el código que se escribe automáticamente.
  6. C# usa el mismo enfoque basado en goto para golpear el mismo bloque desde diferentes etiquetas case como se usa en C. Simplemente lo generaliza a algunos otros casos.
  7. C # hace que ese enfoque basado en goto sea más conveniente y más claro que en C, al permitir que las declaraciones case actúen como etiqueta.

En general, una decisión de diseño bastante razonable


*Algunas formas de BASIC permitirían hacer algo como GOTO (x AND 7) * 50 + 240 que, aunque frágil y por lo tanto un caso particularmente persuasivo para prohibir goto, sirve para mostrar un equivalente de lenguaje superior del tipo de forma en que el código de nivel inferior puede hacer un salto basado en aritmética sobre un valor, que es mucho más razonable cuando es el resultado de la compilación en lugar de algo que debe mantenerse manualmente. Las implementaciones del Dispositivo de Duff en particular se prestan bien al código máquina equivalente o IL porque cada bloque de instrucciones a menudo tendrá la misma longitud sin necesidad de agregar rellenos nop.

†El dispositivo de Duff vuelve a aparecer aquí, como una excepción razonable. El hecho de que con eso y patrones similares hay una repetición de operaciones sirve para hacer el uso de fall-through relativamente claro incluso sin un comentario explícito a ese efecto.

 20
Author: Jon Hanna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-31 14:11:19

Usted puede 'ir a la etiqueta del caso' http://www.blackwasp.co.uk/CSharpGoto.aspx

La instrucción goto es un comando simple que transfiere incondicionalmente el control del programa a otra instrucción. El comando es a menudo criticado con algunos desarrolladores abogando por su eliminación de todos los lenguajes de programación de alto nivel, ya que puede conducir a código de espaguetis. Esto ocurre cuando hay tantas instrucciones goto o instrucciones de salto similares que el código se vuelve difícil de leer y mantener. Sin embargo, hay programadores que señalan que la declaración goto, cuando se usa con cuidado, proporciona una solución elegante a algunos problemas...

 15
Author: kenny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-01 20:29:50

Dejaron fuera este comportamiento por diseño para evitar cuando no fue utilizado por will pero causó problemas.

Se puede usar solo si no hay ninguna instrucción en la parte case, como:

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}
 8
Author: Biri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 13:04:34

Cambiaron el comportamiento de la instrucción switch (de C/Java/C++) para c#. Supongo que el razonamiento fue que la gente se olvidó de la caída y se causaron errores. Un libro que leí decía que usara goto para simular, pero esto no me parece una buena solución.

 4
Author: Ken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 13:03:03

Después de cada sentencia case require break o goto incluso si es un caso predeterminado.

 1
Author: Shilpa gulati,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-04 09:02:02

Una instrucción jump como un break es requerido después de cada bloque de caja, incluyendo el último bloque si es una sentencia case o un valor predeterminado instrucción. Con una excepción, (a diferencia de la sentencia C++ switch), C# no apoyar una caída implícita a través de etiqueta de un caso a otro. Aquel la excepción es si una sentencia case tiene no hay código.

-- Documentación de C # switch ()

 0
Author: Powerlord,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 13:03:34

C# no soporta fall through con sentencias switch/case. No estoy seguro de por qué, pero realmente no hay apoyo para ello. Vinculación

 0
Author: BFree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 13:04:09

Puedes lograr caer como c++ con la palabra clave 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;
}
 0
Author: Dai Tran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-28 20:30:50

Solo una nota rápida para agregar que el compilador para Xamarin realmente se equivocó y permite fallthrough. Supuestamente ha sido arreglado, pero no ha sido liberado. Descubrió esto en algún código que en realidad estaba cayendo y el compilador no se quejó.

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-14 22:19:05

Switch (Referencia C#) dice

C # requiere el final de las secciones del interruptor, incluyendo la final,

Así que también necesita agregar un break; a su sección default, de lo contrario todavía habrá un error del compilador.

 0
Author: gm2008,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-07 22:53:24

Se olvidó de agregar la instrucción "break;" en el caso 3. En el caso 2 lo escribiste en el bloque if. Por lo tanto, prueba esto:

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");
}
 -12
Author: Marcus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 13:10:37