Existe alguma diferença significativa entre o uso de if / else e switch-case em c #?

Qual é o benefício / desvantagem de usar uma instrução switch vs. um if/else em C #. Eu não posso imaginar que haja uma grande diferença, além da aparência do seu código.

Existe alguma razão pela qual o IL resultante ou o desempenho de tempo de execução associado seria radicalmente diferente?

Relacionado: O que é mais rápido, ligue string ou elseif no tipo?

Instrução SWITCH só produz o mesmo assembly como IFs no modo de debugging ou compatibilidade. Na versão, ele será compilado na tabela de salto (por meio da instrução ‘switch’ MSIL) – que é O (1).

C # (ao contrário de muitos outros idiomas) também permite ativar constantes de string – e isso funciona de forma um pouco diferente. Obviamente, não é prático construir tabelas de salto para strings de comprimentos arbitrários, então, na maioria das vezes, esse switch será compilado em uma pilha de IFs.

Mas, se o número de condições for grande o suficiente para cobrir as despesas gerais, o compilador C # criará um object HashTable, preencherá com constantes de string e fará uma pesquisa nessa tabela seguida de salto. A consulta de hash não é estritamente O (1) e tem custos constantes notáveis, mas se o número de labels de caso for grande, será significativamente mais rápido do que comparar com cada constante de sequência nos IFs.

Para resumir, se o número de condições for maior que 5 ou mais, prefira alternar entre IF, caso contrário, use o que parecer melhor.

Em geral (considerando todas as linguagens e todos os compiladores), uma instrução switch PODE, ALGUMAS vezes, ser mais eficiente que uma instrução if / else, porque é fácil para um compilador gerar tabelas de salto a partir de instruções switch. É possível fazer a mesma coisa para instruções if / else, dadas as restrições apropriadas, mas isso é muito mais difícil.

No caso do C #, isso também é verdade, mas por outros motivos.

Com um grande número de strings, há uma vantagem significativa no desempenho ao usar uma instrução switch, porque o compilador usará uma tabela de hash para implementar o salto.

Com um pequeno número de strings, o desempenho entre os dois é o mesmo.

Isso ocorre porque nesse caso o compilador C # não gera uma tabela de salto. Em vez disso, ele gera MSIL equivalente aos blocos IF / ELSE.

Há uma instrução MSIL “switch statement” que, quando usada, usará uma tabela de salto para implementar uma instrução switch. Ele só funciona com tipos inteiros, no entanto (esta pergunta é sobre strings).

Para um pequeno número de strings, é mais eficiente para o compilador gerar blocos IF / ELSE, então é usar uma tabela de hash.

Quando notei isso originalmente, presumi que, como os blocos IF / ELSE eram usados ​​com um pequeno número de strings, o compilador fazia a mesma transformação para um grande número de strings.

Isso foi errado. ‘IMA’ teve a gentileza de apontar isso para mim (bem … ele não foi gentil sobre isso, mas ele estava certo, e eu estava errado, que é a parte importante)

Eu também fiz uma suposição com base na falta de uma instrução “switch” no MSIL (imaginei que, se houvesse uma chave primitiva, por que eles não a usavam com uma tabela de hash, então não deveria haver uma chave primitiva. …) Isso era errado e incrivelmente idiota da minha parte. Mais uma vez, ‘IMA’ apontou isso para mim.

Fiz as atualizações aqui porque é o post mais bem classificado e a resposta aceita.

No entanto, criei o Community Wiki porque acho que não mereço o REP por estar errado. Se você tiver uma chance, por favor, vote no post do ima.

Três razões para preferir a switch :

  • Um compilador que alveja código nativo pode freqüentemente compilar uma instrução switch em uma ramificação condicional mais um salto indireto, enquanto uma seqüência de if requer uma seqüência de ramificações condicionais . Dependendo da densidade dos casos, muitos artigos aprendidos foram escritos sobre como compilar as declarações de casos de forma eficiente; alguns estão ligados a partir da página do compilador lcc . (A Lcc tinha um dos compiladores mais inovadores para switches.)

  • Uma instrução switch é uma escolha entre alternativas mutuamente exclusivas e a syntax de troca torna esse stream de controle mais transparente para o programador do que um conjunto de instruções if-then-else.

  • Em alguns idiomas, incluindo ML e Haskell, o compilador verifica se você omitiu algum caso . Eu vejo esse recurso como uma das principais vantagens de ML e Haskell. Eu não sei se o C # pode fazer isso.

Uma anedota: em uma palestra que ele deu ao receber um prêmio para a realização da vida, ouvi Tony Hoare dizer que de todas as coisas que ele fez em sua carreira, havia três que ele estava mais orgulhoso:

  • Inventando o Quicksort
  • Inventando a instrução switch (que Tony chamou de declaração de case )
  • Começando e terminando sua carreira na indústria

Não consigo imaginar viver sem switch .

Na verdade, uma declaração de switch é mais eficiente. O compilador irá otimizá-lo para uma tabela de pesquisa onde, com as instruções if / else, não pode. O lado negativo é que uma instrução switch não pode ser usada com valores de variables.
Você não pode fazer

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

tem que ser

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

O compilador vai otimizar praticamente tudo no mesmo código com pequenas diferenças (Knuth, alguém?).

A diferença é que uma instrução switch é mais limpa do que quinze, se outras instruções forem juntas.

Os amigos não permitem que os amigos empilhem as declarações if-else.

Eu não vi ninguém mais levantar o ponto (óbvio?) De que a suposta vantagem de eficiência da instrução switch depende de os vários casos serem aproximadamente igualmente prováveis. Nos casos em que um (ou alguns) dos valores são muito mais prováveis, a ladder if-then-else pode ser muito mais rápida, garantindo que os casos mais comuns sejam verificados primeiro:

Então, por exemplo:

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

Se x for zero 90% do tempo, o código “if-else” poderá ser duas vezes mais rápido que o código baseado em switch. Mesmo se o compilador transformar o “switch” em algum tipo de goto inteligente baseado em tabela, ele ainda não será tão rápido quanto simplesmente verificar por zero.

muitas vezes parecerá melhor – ou seja, será mais fácil entender o que está acontecendo. Considerando que o benefício de desempenho será extremamente mínimo na melhor das hipóteses, a visão do código é a diferença mais importante.

Então, se o if / else parecer melhor, use-o, caso contrário, use uma instrução switch.

Tópico de lado, mas muitas vezes me preocupo com (e mais frequentemente ver) if / else e switch declaração ficar muito grande com muitos casos. Estes geralmente prejudicam a manutenção.

Os culpados comuns incluem:

  1. Fazendo muito dentro de múltiplas declarações if
  2. Mais declarações de caso do que humanamente possíveis para analisar
  3. Muitas condições na avaliação para saber o que está sendo procurado

Consertar:

  1. Extraia para Refatoração do Método.
  2. Use um dictionary com pointers de método em vez de um caso ou use um IoC para configuração adicional. Fábricas de methods também podem ser úteis.
  3. Extraia as condições para o seu próprio método

Se você está apenas usando if ou mais instrução a solução base está usando o comparsion? operador

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

Você pode fazer a rotina ou em um switch

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

Isso na verdade não responde à sua pergunta, mas, dado que haverá pouca diferença entre as versões compiladas, peço-lhe que escreva seu código de uma maneira que melhor descreva suas intenções. Não só há uma chance melhor de o compilador fazer o que você espera, mas também tornará mais fácil para os outros manterem seu código.

Se sua intenção é ramificar seu programa com base no valor de uma variável / atributo, então uma declaração de switch melhor representa essa intenção.

Se a sua intenção é ramificar seu programa com base em diferentes variables ​​/ atributos / condições, então uma cadeia if / else if representa melhor essa intenção.

Eu garantirei que o cody está certo sobre as pessoas esquecerem o comando break, mas quase com a mesma frequência eu vejo as pessoas fazendo complicadas se os blocos ficarem {} errados, então as linhas que deveriam estar na instrução condicional não estão. É uma das razões pelas quais eu sempre incluo {} nas minhas declarações if, mesmo que haja uma linha nela. Não só é mais fácil de ler, mas se eu precisar adicionar outra linha no condicional, não posso esquecer de adicioná-la.

A instrução switch é definitivamente mais rápida do que se. Há speedtest que foram fornecidos pelo BlackWasp

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

–Confira

Mas depende muito das possibilidades que você está tentando explicar, mas eu tento usar uma instrução switch sempre que possível.

Não apenas C #, mas todas as linguagens baseadas em C, eu acho: porque um switch é limitado a constantes, é possível gerar código muito eficiente usando uma “tabela de salto”. O caso C é realmente um bom velho FORTRAN computado GOTO, mas o caso C # ainda é testes contra uma constante.

Não é o caso de o otimizador conseguir fazer o mesmo código. Considere, por exemplo,

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

Porque esses são compostos booleanos, o código gerado tem que calcular um valor e curto-circuito. Agora considere o equivalente

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

Isso pode ser compilado em

 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 você está implicitamente dizendo ao compilador que ele não precisa computar os testes OR e de igualdade.

Questão de interesse. Isso surgiu há algumas semanas no trabalho e encontramos uma resposta escrevendo um trecho de exemplo e visualizando-o no .NET Reflector (refletor é incrível! Eu adoro isso).

Isso é o que descobrimos: Uma instrução switch válida para qualquer coisa diferente de uma string é compilada para o IL como uma instrução switch. No entanto, se for uma string, ela será reescrita como if / else if / else em IL. Então, no nosso caso, nós queríamos saber como as declarações switch comparam strings, por exemplo, é sensível a maiúsculas e minúsculas, etc. O refletor rapidamente nos deu uma resposta. Isso foi útil para saber.

Se você quiser fazer uma comparação com distinção entre maiúsculas e minúsculas em strings, poderá usar uma instrução switch, pois ela é mais rápida do que executar uma String. Compare em if / else. (Edit: Read O que é mais rápido, ligue string ou elseif no tipo? Para alguns testes de desempenho reais) No entanto, se você quiser fazer uma distinção entre maiúsculas e minúsculas, é melhor usar um if / else como o código resultante não é bonito.

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

A melhor regra é usar instruções switch, se fizer sentido (sério), por exemplo:

  • melhora a legibilidade do seu código
  • você está comparando um intervalo de valores (float, int) ou um enum

Se você precisar manipular o valor para alimentar a instrução switch (crie uma variável temporária para alternar), provavelmente você deve estar usando uma instrução de controle if / else.

Uma atualização:

Na verdade, é melhor converter a string para maiúscula (por exemplo, ToUpper() ), já que aparentemente há mais otimizações que o compilador just-in-time pode fazer como quando comparado ao ToLower() . É uma otimização micro, no entanto, em um loop apertado, pode ser útil.


Uma pequena nota lateral:

Para melhorar a legibilidade das instruções switch, tente o seguinte:

  • colocar o ramo mais provável primeiro ou seja, mais acessado
  • se todas elas ocorrerem, liste-as em ordem alfabética, para que seja mais fácil encontrá-las.
  • nunca use o pega-pega padrão para a última condição restante, que é preguiçoso e causará problemas mais tarde na vida do código.
  • use o pega-pega padrão para confirmar uma condição desconhecida, embora seja altamente improvável que ela ocorra. é para isso que as afirmações são boas.

De acordo com este link, a comparação IF vs Switch do teste de iteração usando switch e if, é semelhante a 1.000.000.000 de iterações, o tempo gasto por Switch Statement = 43.0s e If Statement = 48.0s

Que é literalmente 20833333 iterações por segundo, Então, nós realmente precisamos nos concentrar mais,

PS: Só para saber a diferença de desempenho para a pequena lista de condições.

Meu professor cs sugeriu que você não mudasse as instruções, pois muitas vezes as pessoas esquecem o intervalo ou o usam incorretamente. Não consigo me lembrar exatamente do que ele disse, mas algo nos moldes que olha para algum código seminal que mostrava exemplos da instrução switch (anos atrás) também tinha toneladas de erros.

Algo que eu notei é que você pode combinar if / else e trocar instruções! Muito útil quando precisar verificar condições prévias.

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

Eu acho que o switch é mais rápido do que se as condições como ver se há um programa como:

Escreva um programa para inserir qualquer número (entre 1 – 99) e verifique se está em qual slot a) 1 – 9, em seguida, fenda 1 b) 11 – 19 então fenda dois c) 21-29 então fenda três e assim por diante até 89- 99

Então, se você tem que fazer muitas condições, mas caso você mudar de filho você tem que apenas digitar

Interruptor (não / 10)

e no caso 0 = 1-9, caso 1 = 11-19 e assim por diante

será tão fácil

Existem muitos outros exemplos também!

Uma declaração switch é basicamente uma comparação para igualdade. Os events de teclado têm uma grande vantagem sobre a instrução switch quando é fácil escrever e ler código, em seguida, se uma instrução ififif, a falta de um {colchete} também pode ser problemática.

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

Uma declaração ififif é ótima para mais de uma solução se (theAmountOfApples for maior que 5 && theAmountOfApples for menor que 10) salve suas maçãs else if (theAmountOfApples for maior que 10 || theAmountOfApples == 100) venda suas maçãs. Eu não escrevo c # ou c ++, mas eu aprendi antes de aprender java e eles são idiomas próximos.

Eu sei que isso não é exatamente a pergunta, mas eu realmente preciso ressaltar que, quando você pensa em eficiência nesse nível, pode precisar de mais abstração em seu código. Você não precisará mais de casos de switch, especialmente se contiver lógica. (meu exemplo em php).

  $feeMapping = [ 1000 => 1, 2000 => 2, 3000 => 3, 4000 => 4, 5000 => 5, 6000 => 6, 7000 => 7 ]; function findFee($feeMapping, $amount) { foreach ($feeMapping as $fee => $value) { if ($value >= $amount) { return $fee; } } return 7; } $feeValue = findFee($feeMapping, 200); 

Agora olhe para a redundância do código semelhante!

  if ($amount >= 1000) { return 1; } elseif ($amount >= 2000) { return 2; } elseif ($amount >= 3000) { return 3; } elseif ($amount >= 4000) { return 4; } elseif ($amount >= 5000) { return 5; } elseif ($amount >= 6000) { return 6; } else { return 7; } 

Uma possível desvantagem das instruções switch é a falta de várias condições. Você pode ter várias condições para as instruções if (else), mas não múltiplas, com condições diferentes em um comutador.

As instruções switch não são adequadas para operações lógicas além do escopo de equações / expressões booleanas simples. Para essas equações / expressões booleanas, é eminentemente adequado, mas não para outras operações lógicas.

Você tem muito mais liberdade com a lógica disponível em instruções If, mas a legibilidade pode ser prejudicada se a instrução If se tornar pesada ou mal tratada.

Ambos têm lugar dependendo do contexto do que você está enfrentando.