multiple - Cambiar la declaración fallida en C#?
switch case c++ (14)
Se requiere una declaración de salto, como una interrupción, después de cada bloque de caso, incluido el último bloque, ya sea una declaración de caso o una declaración predeterminada. Con una excepción, (a diferencia de la instrucción de cambio de C ++), C # no admite una caída implícita de una etiqueta de caso a otra. La única excepción es si una declaración de caso no tiene código.
La declaración de cambio es una de mis principales razones personales para el switch
amor frente a if/else if
construye. 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;
}
La gente inteligente se está encogiendo porque la string[]
s debe 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 if
s?
(Copia / pega de una respuesta que proporcioné en otro lugar )
La caída a través del switch
case
y case
se puede lograr al no tener código en un case
(ver case 0
), o usar los goto case
especiales de 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;
}
C # no es compatible con las declaraciones de cambio / caso. No estoy seguro de por qué, pero realmente no hay apoyo para ello. Linkage
Cambiaron el comportamiento de la instrucción de cambio (de C / Java / C ++) para c #. Supongo que el razonamiento fue que las personas se olvidaron de la caída y los errores fueron causados. Un libro que leí dice que usa goto para simular, pero esto no me suena como una buena solución.
Dejaron de lado este comportamiento por diseño para evitarlo cuando no fue utilizado por la voluntad pero causó problemas.
Se puede usar solo si no hay ninguna declaración en la parte del caso, como:
switch (whatever)
{
case 1:
case 2:
case 3: boo; break;
}
Después de cada declaración de caso, se requiere una declaración de ruptura o goto incluso si es un caso predeterminado.
El "por qué" es evitar la caída accidental, por lo que estoy agradecido. Esta es una fuente común 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 del interruptor / caja es un poco desafortunado en mi opinión. Se pegó demasiado cerca de C: hay algunos cambios útiles que podrían hacerse en términos de alcance, etc. Posiblemente, un cambio más inteligente que podría hacer la coincidencia de patrones, etc. sería útil, pero eso realmente cambia de "cambio a una secuencia de condiciones" - en qué punto tal vez se necesitaría un nombre diferente.
La conmutación de fallos es históricamente una de las principales fuentes de errores en los softwares modernos. El diseñador de idiomas decidió hacer que sea obligatorio saltar al final del caso, a menos que esté en forma predeterminada al siguiente caso directamente sin procesamiento.
switch(value)
{
case 1:// this is still legal
case 2:
}
Olvidaste agregar el "break"; declaración 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");
}
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 dos objetivos:
- Proporcionar instrucciones a la computadora.
- Deja 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 estos dos objetivos. Por un lado, cuanto más fácil sea convertirlas en instrucciones de computadora (ya sean códigos de máquina, códigos de byte como IL o las instrucciones que se interpretan en la ejecución), entonces más capaz será el proceso de compilación o interpretación para ser eficiente, confiable y eficiente. Compacto en la salida. Llevado a su extremo, este objetivo se traduce en que solo escribamos en ensamblador, IL o incluso códigos de operación sin formato, porque la compilación más sencilla es donde no hay ninguna compilación.
A la inversa, cuanto más el lenguaje expresa la intención del programador, en lugar de los medios tomados para ese fin, más comprensible es el programa tanto al escribir como durante el mantenimiento.
Ahora, el switch
siempre se podría haber compilado convirtiéndolo en la cadena equivalente de bloques if-else
o similares, pero se diseñó para permitir la compilación en un patrón de ensamblaje común particular donde uno toma un valor, calcula un desplazamiento a partir de él (ya sea por buscando una tabla indexada por un hash perfecto del valor, o por aritmética real en el valor *). En este punto, vale la pena señalar que hoy en día, la compilación de C # a veces switch
a switch
en el equivalente if-else
, y a veces utilizará un enfoque de salto basado en hash (y también con C, C ++ y otros lenguajes con una sintaxis comparable).
En este caso, hay dos buenas razones para permitir la caída:
De todos modos, simplemente sucede de forma natural: si construyes una tabla de saltos en un conjunto de instrucciones, y uno de los lotes de instrucciones anteriores no contiene algún tipo de salto o retorno, la ejecución avanzará naturalmente hacia el siguiente lote. Permitir la caída fue lo que "solo sucedería" si
switch
elswitch
, usando C en la tabla de salto, usando el código de la máquina.Los codificadores que escribieron en ensamblaje ya estaban acostumbrados al equivalente: al escribir una tabla de salto a mano en ensamblaje, tendrían que considerar si un bloque de código dado terminaría con una devolución, un salto fuera de la tabla o simplemente continuaría al siguiente bloque. Como tal, hacer que el codificador agregue una
break
explícita cuando sea necesario también fue "natural" para el codificador.
En ese momento, por lo tanto, fue un intento razonable de equilibrar los dos objetivos de un lenguaje informático en lo que se refiere tanto al código de máquina producido como a la expresividad del código fuente.
Sin embargo, cuatro décadas después, las cosas no son exactamente iguales, por varias razones:
- Los codificadores en C hoy pueden tener poca o ninguna experiencia en ensamblaje. Los codificadores en muchos otros lenguajes de estilo C son incluso menos propensos a hacerlo (¡especialmente Javascript!). Cualquier concepto de "a lo que la gente está acostumbrada desde el montaje" ya no es relevante.
- Las mejoras en las optimizaciones significan que la probabilidad de que el
switch
se convierta enif-else
debido a que se consideró que el enfoque es más eficiente o que se convierte en una variante particularmente esotérica del enfoque de la tabla de saltos es mayor. El mapeo entre los enfoques de nivel superior e inferior no es tan fuerte como lo era antes. - La experiencia ha demostrado que las caídas tienden a ser el caso minoritario en lugar de la norma (un estudio del compilador de Sun encontró que el 3% de los bloques de
switch
utilizaban una trampa distinta a múltiples etiquetas en el mismo bloque, y se pensó que el uso -caso aquí significó que este 3% era en realidad mucho más alto de lo normal). Así que el lenguaje estudiado hace que lo inusual sea más fácil de atender que lo común. - La experiencia ha demostrado que la caída tiende a ser la fuente de problemas, tanto en los casos en que se realiza accidentalmente, como en los casos en que alguien que mantiene el código omite la caída correcta. Esta última es una adición sutil a los errores asociados con la caída, ya que incluso si su código está perfectamente libre de errores, su caída puede causar problemas.
En relación con esos dos últimos puntos, considere la siguiente cita de la edición actual de K&R:
La caída de un caso a otro no es robusta, ya que es propensa a la desintegración cuando se modifica el programa. Con la excepción de las etiquetas múltiples para un solo cálculo, los pasajes deben usarse con moderación y comentarse.
Como cuestión de buena forma, ponga una pausa después del último caso (el valor predeterminado aquí) aunque sea lógicamente innecesario. Algún día, cuando se agregue otro caso al final, este poco de programación defensiva lo salvará.
Entonces, desde la boca del caballo, la caída en C es problemática. Se considera una buena práctica documentar siempre los fallos con los comentarios, que es una aplicación del principio general de que uno debe documentar dónde se hace algo inusual, porque eso es lo que hará el examen posterior del código y / o hará que su código se vea así. tiene un error de principiante cuando en realidad es correcto.
Y cuando lo pienses, codifica así:
switch(x)
{
case 1:
foo();
/* FALLTHRU */
case 2:
bar();
break;
}
Es agregar algo para hacer explícito el paso a paso 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 debe ser explícito con el paso a paso 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 su paso a paso. †
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 caso en el que queremos que se incluya un bloque en el código ejecutado por un valor distinto al que trae 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 todo lo relacionado con esta pregunta). Como tal, C # se basó en la manera 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 generalizamos para cubrir también la caída. También hizo que ambos casos fueran más convenientes y autodocumentados, 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 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 el código C #).
En resumen por lo tanto:
- C # ya no se relaciona con la salida del compilador no optimizada como lo hizo el código C hace 40 años (ni C en estos días), lo que hace que una de las inspiraciones de la caída irrelevante sea irrelevante.
- C # sigue siendo compatible con C, ya que no solo tiene una
break
implícita, para un aprendizaje más fácil del idioma por parte de personas familiarizadas con idiomas similares y una adaptación más fácil. - 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.
- C # hace que las mejores prácticas existentes con C (el documento no se cumpla) sean ejecutables por el compilador.
- C # hace que el caso inusual sea el que tiene un código más explícito, el caso habitual el que tiene el código que uno simplemente escribe automáticamente.
- C # utiliza el mismo enfoque basado en
goto
para golpear el mismo bloque desde diferentes etiquetas decase
como se usa en C. Simplemente lo generaliza a otros casos. - C # hace que el enfoque basado en
goto
más conveniente y más claro que en C, al permitircase
declaraciones decase
actúen como etiquetas.
En definitiva, una decisión de diseño bastante razonable.
* Algunas formas de BASIC nos permitirían hacer GOTO (x AND 7) * 50 + 240
que, aunque frágil y por lo tanto un caso particularmente persuasivo para prohibir el goto
, sirve para mostrar un equivalente de lenguaje superior del tipo de manera ese código de nivel inferior puede realizar un salto basado en la 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 de 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 haya una repetición de operaciones sirve para hacer que el uso de la caída sea relativamente claro, incluso sin un comentario explícito en ese sentido.
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 a menudo se critica y algunos desarrolladores abogan por que se elimine de todos los lenguajes de programación de alto nivel porque puede llevar al código spaghetti . Esto ocurre cuando hay tantas declaraciones goto o frases similares que hacen que el código se vuelva difícil de leer y mantener. Sin embargo, hay programadores que señalan que la instrucción goto, cuando se usa con cuidado, proporciona una solución elegante para algunos problemas ...
Puede 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;
}
Solo una nota rápida para agregar que el compilador de Xamarin en realidad se equivocó y que permite su paso. Supuestamente ha sido arreglado, pero no ha sido lanzado. Descubrí esto en un código que realmente estaba fallando y el compilador no se quejó.
interruptor (Referencia C #) dice
C # requiere el final de las secciones del interruptor, incluida la última,
Así que también necesitas agregar un break;
a su sección default
, de lo contrario todavía habrá un error de compilación.