¿Hay alguna diferencia significativa entre usar if / else y switch-case en C #?

219

¿Cuál es el beneficio / desventaja de usar una switchdeclaración vs. an if/elseen C #? No puedo imaginar que haya una diferencia tan grande, aparte de tal vez la apariencia de su código.

¿Hay alguna razón por la cual la IL resultante o el rendimiento del tiempo de ejecución asociado serían radicalmente diferentes?

Relacionado: ¿Qué es más rápido, activar cadena o elseif en tipo?

Matthew M. Osborn
fuente
3
Esta pregunta solo es interesante en teoría para la mayoría de los desarrolladores, a menos que a menudo te encuentres iterando UN MIL MILLÓN de veces. (Luego use una declaración de cambio y pase de 48 a 43 segundos ...) O en palabras de Donald Knuth: "Deberíamos olvidarnos de pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal". en.wikipedia.org/wiki/Program_optimization#When_to_optimize
Sire
A menudo uso if / else en lugar de switch debido al alcance compartido del interruptor.
Josh

Respuestas:

341

La instrucción SWITCH solo produce el mismo ensamblaje que los IF en modo de depuración o compatibilidad. En el lanzamiento, se compilará en la tabla de salto (a través de la declaración 'switch' de MSIL), que es O (1).

C # (a diferencia de muchos otros lenguajes) también permite activar constantes de cadena, y esto funciona de manera un poco diferente. Obviamente, no es práctico construir tablas de salto para cadenas de longitudes arbitrarias, por lo que la mayoría de las veces este cambio se compilará en una pila de IF.

Pero si el número de condiciones es lo suficientemente grande como para cubrir los gastos generales, el compilador de C # creará un objeto HashTable, lo completará con constantes de cadena y realizará una búsqueda en esa tabla seguida de un salto. La búsqueda de hash no es estrictamente O (1) y tiene costos constantes notables, pero si el número de etiquetas de mayúsculas y minúsculas es grande, será significativamente más rápido que comparar cada constante de cadena en los IF.

Para resumir, si el número de condiciones es más de 5 más o menos, prefiera CAMBIAR sobre SI, de lo contrario, use lo que se vea mejor.

ima
fuente
1
¿Estás seguro de que el compilador de C # producirá una tabla hash? El punto que hice sobre la tabla hash en nuestra discusión de comentarios anterior fue sobre los compiladores nativos, no el compilador de C #. ¿Qué umbral utiliza el compilador de C # para producir una tabla hash?
Scott Wisniewski
8
Sobre las diez, creo. 20 para estar en el lado seguro. Y por cierto, mi enojo no eres tú, sino por las personas que votan y aceptan.
ima
48
Un poco de experimentación sugiere contar <= 6: "if"; cuenta> = 7: diccionario. Eso es con el compilador MS .NET 3.5 C #: por supuesto, podría cambiar entre versiones y proveedores.
Jon Skeet
37
Oye, te debo una disculpa. Perdón por ser una cabeza de hueso.
Scott Wisniewski
Como seguimiento, para aplicaciones prácticas, ¿hay alguna diferencia en el mundo real la mayor parte del tiempo? Encuentro que las declaraciones de cambio en C # son extrañas, su sintaxis no es como ninguna otra cosa y encuentro que hacen que mi código sea menos legible, vale la pena molestarme en usar declaraciones de cambio, o debería simplemente programar con ifs más y solo venir volver y reemplazarlos si llego a cuellos de botella de rendimiento?
Jason Masters el
54

En general (considerando todos los idiomas y todos los compiladores), una declaración de cambio PUEDE SER A VECES más eficiente que una declaración de if / else, porque es fácil para un compilador generar tablas de salto a partir de declaraciones de cambio. Es posible hacer lo mismo para las declaraciones if / else, dadas las restricciones apropiadas, pero eso es mucho más difícil.

En el caso de C #, esto también es cierto, pero por otras razones.

Con una gran cantidad de cadenas, existe una ventaja de rendimiento significativa al usar una instrucción switch, porque el compilador usará una tabla hash para implementar el salto.

Con un pequeño número de cadenas, el rendimiento entre los dos es el mismo.

Esto se debe a que en ese caso el compilador de C # no genera una tabla de salto. En su lugar, genera MSIL que es equivalente a los bloques IF / ELSE.

Hay una instrucción MSIL de "declaración de cambio" que, cuando se modifique, utilizará una tabla de salto para implementar una declaración de cambio. Sin embargo, solo funciona con tipos enteros (esta pregunta se refiere a cadenas).

Para pequeñas cantidades de cadenas, es más eficiente que el compilador genere bloques IF / ELSE que usar una tabla hash.

Cuando noté esto originalmente, asumí que debido a que los bloques IF / ELSE se usaban con una pequeña cantidad de cadenas, el compilador hizo la misma transformación para grandes cantidades de cadenas.

Esto estaba mal. 'IMA' tuvo la amabilidad de señalarme esto (bueno ... él no fue amable al respecto, pero tenía razón y yo estaba equivocado, lo cual es la parte importante)

También hice una suposición descabellada sobre la falta de una instrucción de "cambio" en MSIL (supuse que, si había una primitiva de conmutación, ¿por qué no la usaban con una tabla hash? Por lo tanto, no debe haber una primitiva de conmutación). ...) Esto estaba mal, e increíblemente estúpido de mi parte. Nuevamente, 'IMA' me señaló esto.

Hice las actualizaciones aquí porque es la publicación mejor calificada y la respuesta aceptada.

Sin embargo, lo hice Community Wiki porque creo que no merezco el REP por estar equivocado. Si tienes la oportunidad, vota la publicación de 'ima'.

Scott Wisniewski
fuente
3
Hay una primitiva de conmutación en MSIL, y las declaraciones de c # se compilan en una búsqueda generalmente similar a C. Bajo ciertas circunstancias (plataforma de destino, conmutadores cl, etc.), el conmutador puede expandirse a IF durante la compilación, pero es solo una medida de compatibilidad de respaldo.
ima
66
Todo lo que puedo hacer es disculparme por cometer un error estúpido. Créeme, me siento tonto por eso. En serio, creo que sigue siendo la mejor respuesta. Es posible, en compiladores nativos, usar una tabla hash para implementar un salto, por lo que esto no es algo terriblemente incorrecto. Cometí un error.
Scott Wisniewski
99
ima, si hay errores, indíquelos. Parece que Scott estará encantado de corregir la publicación. Si no, otros que hayan alcanzado la capacidad de corregir una respuesta lo harán. Esa es la única forma en que un sitio como este funcionará, y parece que, en general, está funcionando. O toma tu pelota y vete a casa :)
jwalkerjr
2
@Scott: Le animo a editar el segundo y tercer párrafo para indicar explícitamente "para cadenas". Es posible que la gente no lea la actualización en la parte inferior.
Jon Skeet
44
@ima Si cree que una respuesta es objetivamente incorrecta, edítela para que sea correcta. Es por eso que todos pueden editar las respuestas.
Miles Rout
18

Tres razones para preferir el switch:

  • Un compilador que apunta al código nativo a menudo puede compilar una declaración de cambio en una rama condicional más un salto indirecto, mientras que una secuencia de ifs requiere una secuencia de ramas condicionales . Dependiendo de la densidad de casos, se han escrito una gran cantidad de documentos aprendidos sobre cómo compilar declaraciones de casos de manera eficiente; algunos están vinculados desde la página del compilador de lcc . (Lcc tenía uno de los compiladores más innovadores para conmutadores).

  • Una instrucción switch es una elección entre alternativas mutuamente excluyentes y la sintaxis del interruptor hace que este control fluya más transparente para el programador que un conjunto de instrucciones if-then-else.

  • En algunos idiomas, incluidos definitivamente ML y Haskell, el compilador verifica si ha omitido algún caso . Veo esta característica como una de las principales ventajas de ML y Haskell. No sé si C # puede hacer esto.

Una anécdota: en una conferencia que pronunció al recibir un premio por su trayectoria, escuché a Tony Hoare decir que de todas las cosas que hizo en su carrera, había tres de las que estaba más orgulloso:

  • Inventar Quicksort
  • Inventar la declaración de cambio (que Tony llamó la casedeclaración)
  • Comenzando y terminando su carrera en la industria

No me puedo imaginar vivir sin élswitch .

Norman Ramsey
fuente
16

El compilador va a optimizar casi todo en el mismo código con pequeñas diferencias (Knuth, ¿alguien?).

La diferencia es que una declaración de cambio es más limpia que quince si las declaraciones encadenan juntas.

Los amigos no permiten que los amigos apilen declaraciones if-else.


fuente
13
"Los amigos no dejan que los amigos acumulen declaraciones if-else". Deberías hacer un sitcker bumber :)
Matthew M. Osborn
14

En realidad, una declaración de cambio es más eficiente. El compilador lo optimizará a una tabla de búsqueda donde con sentencias if / else no puede. La desventaja es que una declaración de cambio no se puede usar con valores variables.
No puedes hacer:

switch(variable)
{
   case someVariable
   break;
   default:
   break;
}

tiene que ser

switch(variable)
{
  case CONSTANT_VALUE;
  break;
  default:
  break;
}
kemiller2002
fuente
1
¿tienes algún tipo de números? Tengo curiosidad por saber qué tan bien un compilador podría optimizar una declaración rápida sobre un If / Else
Matthew M. Osborn
sí, creo que una instrucción switch siempre se optimiza a O (1) donde una instrucción if else será O (n) donde n es la posición del valor correcto en las instrucciones if / else if.
kemiller2002
En el caso de C # esto no es cierto, consulte mi publicación a continuación para obtener más información.
Scott Wisniewski
No estoy totalmente seguro de eso, pero no puedo encontrar información en el libro, juro que lo encontré. ¿Estás seguro de que no estás mirando el código MSIL sin optimización? No creará la tabla de salto a menos que compile con la optimización activada.
kemiller2002
Compilé tanto en modo de depuración como en modo minorista, y en ambos casos genera bloques if / else. ¿Estás seguro de que el libro que estabas viendo hablaba de C #? El libro probablemente era un libro compilador o un libro sobre C o C ++
Scott Wisniewski,
14

No vi a nadie más plantear el punto (¿obvio?) De que la supuesta ventaja de eficiencia de la declaración de cambio depende de que los diversos casos sean aproximadamente igualmente probables. En los casos en que uno (o unos pocos) de los valores son mucho más probables, la escalera if-then-else puede ser mucho más rápida, asegurando que los casos más comunes se verifiquen primero:

Así por ejemplo:

if (x==0) then {
  // do one thing
} else if (x==1) {
  // do the other thing
} else if (x==2) {
  // do the third thing
}

vs

switch(x) {
  case 0: 
         // do one thing
         break;
  case 1: 
         // do the other thing
         break;
  case 2: 
         // do the third thing
         break;
}

Si x es cero el 90% del tiempo, el código "if-else" puede ser el doble de rápido que el código basado en el interruptor. Incluso si el compilador convierte el "interruptor" en una especie de goto inteligente basado en tablas, no será tan rápido como simplemente verificar cero.

Mark Bessey
fuente
3
¡Sin optimización prematura! En general, si tiene más de unos pocos casos y son switchcompatibles, la switchdeclaración es mejor (más legible, a veces más rápida). Si usted sabe que uno de los casos es mucho más probable, se puede tirar de que fuera para formar un if- else- switchconstrucción y si es mensurable más rápido , dejar que en (Repetir, si es necesario.) De la OMI que todavía es razonablemente fácil de leer.. Si el switchdegenera y se vuelve demasiado pequeño, un reemplazo de expresiones regulares hará la mayor parte del trabajo de transformarlo en una else ifcadena.
nadie
66
La pregunta original (¡hace tres años!) Solo pedía ventajas y desventajas entre if / else y switch. Este es un ejemplo. Personalmente, he visto este tipo de optimización hacer una diferencia significativa en el tiempo de ejecución de una rutina.
Mark Bessey
7

a menudo se verá mejor, es decir, será más fácil entender lo que está sucediendo. Teniendo en cuenta que el beneficio de rendimiento será extremadamente mínimo en el mejor de los casos, la vista del código es la diferencia más importante.

Entonces, si el if / else se ve mejor, úselo, de lo contrario use una instrucción switch.

gbjbaanb
fuente
4

Tema secundario, pero a menudo me preocupo por (y más a menudo veo) if/ elsey la switchdeclaración se vuelve demasiado grande con demasiados casos. Estos a menudo perjudican la mantenibilidad.

Los culpables comunes incluyen:

  1. Hacer demasiado dentro de múltiples declaraciones if
  2. Más declaraciones de casos de las que es humanamente posible analizar
  3. Demasiadas condiciones en la evaluación if para saber qué se está buscando

Arreglar:

  1. Extracto al método de refactorización.
  2. Use un diccionario con punteros de método en lugar de un caso, o use un IoC para una mayor capacidad de configuración. Las fábricas de métodos también pueden ser útiles.
  3. Extraer las condiciones a su propio método.
Chris Brandsma
fuente
4

Según este enlace, la comparación IF vs Switch de la prueba de iteración usando la instrucción switch y if, es como para 1,000,000,000 de iteraciones, el tiempo que toma la declaración Switch = 43.0s y la declaración If = 48.0s

Que es literalmente 20833333 iteraciones por segundo. Entonces, ¿realmente deberíamos enfocarnos más?

PD: Solo para saber la diferencia de rendimiento para una pequeña lista de condiciones.

Bretfort
fuente
Eso me lo clava.
Greg Gum
3

Si solo está usando la declaración if o else, la solución base está usando la comparación. operador

(value == value1) ? (type1)do this : (type1)or do this;

Puedes hacer la rutina en un interruptor

switch(typeCode)
{
   case TypeCode:Int32:
   case TypeCode.Int64:
     //dosomething here
     break;
   default: return;
}
Robert W.
fuente
2

En realidad, esto no responde a su pregunta, pero dado que habrá poca diferencia entre las versiones compiladas, le insto a que escriba su código de la manera que mejor describa sus intenciones. No solo hay una mejor oportunidad de que el compilador haga lo que espera, sino que facilitará que otros mantengan su código.

Si su intención es bifurcar su programa en función del valor de una variable / atributo, una declaración de cambio representa mejor esa intención.

Si su intención es ramificar su programa en función de diferentes variables / atributos / condiciones, entonces una cadena if / else if representa mejor esa intención.

Admitiré que Cody tiene razón acerca de que las personas olvidan el comando de interrupción, pero casi con la misma frecuencia veo que las personas se complican si los bloques obtienen el {} incorrecto, por lo que las líneas que deberían estar en la declaración condicional no lo son. Es una de las razones por las que siempre incluyo {} en mis declaraciones if, incluso si hay una línea en él. No solo es más fácil de leer, sino que si necesito agregar otra línea en el condicional, no puedo olvidar agregarlo.

dj_segfault
fuente
2

Pregunta de interés Esto surgió hace unas semanas en el trabajo y encontramos una respuesta escribiendo un fragmento de ejemplo y viéndolo en .NET Reflector (¡el reflector es increíble! ¡Me encanta!).

Esto es lo que descubrimos: una declaración de cambio válida para cualquier cosa que no sea una cadena se compila en IL como una declaración de cambio. Sin embargo, si es una cadena, se reescribe como if / else if / else en IL. Entonces, en nuestro caso, queríamos saber cómo las declaraciones de cambio comparan cadenas, por ejemplo, distinguen entre mayúsculas y minúsculas, etc. y el reflector rápidamente nos dio una respuesta. Esto fue útil para saber.

Si desea hacer una comparación entre mayúsculas y minúsculas en las cadenas, puede usar una instrucción de cambio, ya que es más rápido que realizar una Cadena. Compare en un if / else. (Editar: Lea ¿Qué es más rápido, encienda la cadena o si no en el tipo? Para algunas pruebas de rendimiento reales) Sin embargo, si desea hacer una distinción entre mayúsculas y minúsculas, es mejor usar un if / else ya que el código resultante no es bonito.

switch (myString.ToLower())
{
  // not a good solution
}

La mejor regla general es usar declaraciones de cambio si tiene sentido (en serio), por ejemplo:

  • Mejora la legibilidad de su código
  • está comparando un rango de valores (float, int) o una enumeración

Si necesita manipular el valor para alimentar la declaración de cambio (crear una variable temporal para cambiar), entonces probablemente debería estar usando una declaración de control if / else.

Una actualización:

En realidad, es mejor convertir la cadena a mayúsculas (por ejemplo ToUpper()), ya que aparentemente ha habido otras optimizaciones que el compilador just-in-time puede hacer en comparación con ToLower(). Es una micro optimización, sin embargo, en un ciclo cerrado podría ser útil.


Una pequeña nota al margen:

Para mejorar la legibilidad de las declaraciones de switch, intente lo siguiente:

  • poner la rama más probable primero, es decir, la más visitada
  • si es probable que ocurran todos, enumérelos en orden alfabético para que sea más fácil encontrarlos.
  • nunca use la opción predeterminada general para la última condición restante, eso es vago y causará problemas más adelante en la vida del código.
  • use el método general predeterminado para afirmar una condición desconocida, aunque es muy poco probable que ocurra alguna vez. para eso son buenas las afirmaciones.
Dennis
fuente
En muchos casos, usar ToLower () es la solución correcta, especialmente si hay muchos casos y se genera la tabla hash.
Blaisorblade
"Si necesita manipular el valor para alimentar la declaración de cambio (cree una variable temporal para cambiar), entonces probablemente debería estar usando una declaración de control if / else". - Buen consejo, gracias.
Sneakyness
2

La declaración de cambio es definitivamente la más rápida que un if if if. Hay pruebas de velocidad que BlackWasp le ha proporcionado.

http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

--Echale un vistazo

Pero depende en gran medida de las posibilidades que está tratando de explicar, pero trato de usar una declaración de cambio siempre que sea posible.

sleath
fuente
1

No solo C #, sino todos los lenguajes basados ​​en C, creo: debido a que un interruptor está limitado a constantes, es posible generar código muy eficiente usando una "tabla de salto". El caso C es realmente un viejo GOTO calculado por FORTRAN, pero el caso C # todavía se prueba contra una constante.

No es el caso de que el optimizador pueda hacer el mismo código. Considere, por ejemplo,

if(a == 3){ //...
} else if (a == 5 || a == 7){ //...
} else {//...
}

Debido a que son booleanos compuestos, el código generado tiene que calcular un valor y un cortocircuito. Ahora considera el equivalente

switch(a){
   case 3: // ...
    break;
   case 5:
   case 7: //...
    break;
   default: //...
}

Esto se puede compilar en

BTABL: *
B3:   addr of 3 code
B5:
B7:   addr of 5,7 code
      load 0,1 ino reg X based on value
      jump indirect through BTABL+x

porque implícitamente le está diciendo al compilador que no necesita calcular las pruebas de OR e igualdad.

Charlie Martin
fuente
No hay razón para que un buen optimizador no pueda manejar el primer código, siempre y cuando el optimizador esté implementado. "El compilador no puede optimizar" solo depende de las diferencias semánticas que solo el ser humano puede conciliar (es decir, si se llama f (), no sabe que f () siempre devuelve 0 o 1).
Blaisorblade
0

Mi profesor de CS sugirió no cambiar las declaraciones, ya que a menudo la gente olvidaba el descanso o lo usaba incorrectamente. No puedo recordar exactamente lo que dijo, pero algo similar a mirar una base de código seminal que mostraba ejemplos de la declaración de cambio (hace años) también tenía toneladas de errores.


fuente
Realmente no es un problema en C #. Vea: stackoverflow.com/questions/174155/… ... y también lea stackoverflow.com/questions/188461/… para una discusión sobre por qué vivir con miedo puede no ser la mejor política ...
Shog9
0

¡Algo que acabo de notar es que puedes combinar if / else y cambiar declaraciones! Muy útil cuando se necesita verificar condiciones previas.

if (string.IsNullOrEmpty(line))
{
    //skip empty lines
}
else switch (line.Substring(0,1))
{
    case "1":
        Console.WriteLine(line);
        break;
    case "9":
        Console.WriteLine(line);
        break;
    default:
        break;
}
Incluso Mien
fuente
3
Sé que esto es viejo, pero creo que técnicamente no estás "combinando" nada. Cada vez que tenga un "else" sin las llaves, se ejecutará la siguiente instrucción. Esa declaración podría ser una declaración de una línea, típicamente mostrada con sangría en la línea siguiente, o declaraciones compuestas como es el caso con if, switch, using, lock, etc. En otras palabras, puede tener "else if", " else switch "," else using ", etc. Dicho esto, me gusta cómo se ve y casi parece intencional. (Descargo de responsabilidad: ¡no he probado todo esto, así que PUEDO estar equivocado!)
Nelson Rothermel
Nelson, estás 100% correcto. Descubrí por qué sucede esto después de haber publicado esta respuesta.
Incluso Mien
0

Creo que Switch es más rápido que si Condiciones como ver si hay un programa como:

Escriba un programa para ingresar cualquier número (entre 1 y 99) y verifique en qué ranura a) 1 - 9 luego ranura uno b) 11-19 y luego ranura dos c) 21-29 luego ranura tres y así hasta 89- 99

Luego, si tiene que hacer muchas condiciones, pero la caja del interruptor de hijo debe escribir

Interruptor (no / 10)

y en el caso 0 = 1-9, caso 1 = 11-19 y así sucesivamente

será tan fácil

¡Hay muchos más ejemplos de este tipo también!

Comunidad
fuente
0

Una declaración de cambio básicamente es una comparación para la igualdad. los eventos de teclado tienen una gran ventaja sobre las instrucciones de cambio cuando tienen un código fácil de escribir y leer y luego una instrucción if elseif, perder un {soporte} también podría ser problemático.

char abc;
switch(abc)
{
case a: break;
case b: break;
case c: break;
case d: break;
}

Una declaración if elseif es ideal para más de una solución si (theAmountOfApples es mayor que 5 && theAmountOfApples es menor que 10) guarde sus manzanas más if (theAmountOfApples es mayor que 10 || theAmountOfApples == 100) venda sus manzanas. No escribo C # o C ++, pero lo aprendí antes de aprender Java y son idiomas cercanos.

Geen
fuente
0

Una posible desventaja de las declaraciones de cambio es su falta de múltiples condiciones. Puede tener múltiples condiciones para el if (else) pero no múltiples declaraciones de casos con diferentes condiciones en un switch.

Las declaraciones de cambio no son adecuadas para operaciones lógicas más allá del alcance de ecuaciones / expresiones booleanas simples. Para esas ecuaciones / expresiones booleanas, es eminentemente adecuado pero no para otras operaciones lógicas.

Usted tiene mucha más libertad con la lógica disponible en las declaraciones If, pero la legibilidad puede verse afectada si la declaración If se vuelve difícil de manejar o se maneja mal.

Ambos tienen lugar según el contexto de lo que se enfrenta.

Neil Meyer
fuente