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

219

Qual é a vantagem / desvantagem de usar uma switchinstrução vs. uma if/elseem C #. Não consigo imaginar a diferença, além da aparência do seu código.

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

Relacionado: O que é mais rápido, ativar a string ou o tipo?

Matthew M. Osborn
fonte
3
Esta questão é interessante apenas em teoria para a maioria dos desenvolvedores, a menos que você se revele frequentemente UM BILHÃO de vezes. (Em seguida, use uma instrução switch e vá de 48 para 43 segundos ...) Ou nas palavras de Donald Knuth: "Devemos esquecer pequenas eficiências, digamos cerca de 97% das vezes: a otimização prematura é a raiz de todo mal" en.wikipedia.org/wiki/Program_optimization#When_to_optimize
Senhor
Costumo usar if / else em vez de switch por causa do escopo compartilhado do switch.
27414 Josh

Respostas:

341

A instrução SWITCH produz apenas o mesmo assembly que os IFs no modo de depuração ou compatibilidade. No lançamento, ele será compilado na tabela de salto (através da instrução 'switch' do MSIL) - que é O (1).

O C # (diferente de muitos outros idiomas) também permite ativar constantes de string - e isso funciona de maneira um pouco diferente. Obviamente, não é prático criar tabelas de salto para sequências de comprimentos arbitrários, portanto, na maioria das vezes, esse comutador será compilado na 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 objeto HashTable, preenchê-lo-á com constantes de seqüência de caracteres e fará uma pesquisa nessa tabela seguida de salto. A pesquisa da tabela de hash não é estritamente O (1) e possui custos constantes perceptíveis, mas se o número de rótulos de maiúsculas for grande, será significativamente mais rápido que comparar com cada constante de string nos FIs.

Para resumir, se o número de condições for superior a 5, prefira SWITCH ao IF, caso contrário, use o que parecer melhor.

Eu sou um
fonte
1
Tem certeza de que o compilador C # produzirá uma tabela de hash? O argumento que fiz sobre a tabela de hash em nossa discussão de comentários acima foi sobre compiladores nativos, não o compilador C #. Qual limite o compilador C # usa para produzir uma tabela de hash?
Scott Wisniewski
8
Cerca de dez, eu acho. 20 para estar no lado seguro. E, a propósito, minha raiva não é você, mas as pessoas que votam e aceitam.
Ima
48
Um pouco de experimentação sugere contagem <= 6: "if"; count> = 7: dicionário. Isso acontece com o compilador MS .NET 3.5 C # - ele pode mudar entre versões e fornecedores, é claro.
Jon Skeet
37
Ei, eu te devo desculpas. Desculpe por ser uma cabeça de osso.
Scott Wisniewski
Como acompanhamento, para aplicações práticas, existe alguma diferença no mundo real na maioria das vezes? Acho as instruções switch em C # estranhas, sua sintaxe não é como qualquer outra coisa e acho que elas tornam meu código menos legível, vale a pena se preocupar em usar instruções switch, ou devo apenas programar com else ifs e apenas vir voltar e substituí-los se eu atingir gargalos de desempenho?
Jason Masters
54

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

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

Com um grande número de cadeias, há uma vantagem significativa de 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, gera MSIL equivalente aos blocos IF / ELSE.

Existe uma instrução MSIL "switch statement" que, quando acionada, usa uma tabela de salto para implementar uma instrução switch. Porém, ele funciona apenas com tipos inteiros (esta pergunta pergunta sobre cadeias).

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

Quando percebi isso originalmente, assumi que, como os blocos IF / ELSE eram usados ​​com um pequeno número de cadeias, o compilador fez a mesma transformação para um grande número de cadeias.

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

Também fiz uma suposição sobre a falta de uma instrução "switch" no MSIL (imaginei que, se havia um primitivo de switch, por que eles não o estavam usando com uma tabela de hash, portanto, não deve haver um primitivo de switch). ...) Isso foi errado e incrivelmente estúpido da minha parte. Mais uma vez 'IMA' apontou isso para mim.

Fiz as atualizações aqui porque é a postagem com a melhor classificação e a resposta aceita.

No entanto, eu criei o Community Wiki porque acho que não mereço o REP por estar errado. Se você tiver uma chance, vote na publicação de 'ima'.

Scott Wisniewski
fonte
3
Há uma primitiva de opção no MSIL, e as instruções c # são compiladas na pesquisa geralmente semelhante a C. Sob certas circunstâncias (plataforma de destino, switches cl etc.), o switch pode ser expandido para os IFs durante a compilação, mas é apenas uma medida de compatibilidade de fallback.
ima
6
Tudo o que posso fazer é me desculpar por cometer um erro estúpido. Acredite, eu me sinto burro por isso. Sério, acho que ainda é a melhor resposta. É possível, em compiladores nativos, usar uma tabela de hash para implementar um salto, portanto isso não é algo terrivelmente errado. Eu cometi um erro.
Scott Wisniewski
9
ima, se houver erros, aponte-os. Parece que Scott ficará feliz em corrigir a postagem. Caso contrário, outras pessoas que tenham conseguido corrigir uma resposta o farão. Essa é a única maneira de um site como esse funcionar e parece que, em geral, está funcionando. Ou tomar a sua bola e ir para casa :)
jwalkerjr
2
@ Scott: Eu encorajo você a editar o segundo e o terceiro parágrafos para declarar explicitamente "para strings". As pessoas podem não ler a atualização na parte inferior.
Jon Skeet
4
@ima Se você acha que uma resposta está objetivamente errada, edite-a para que esteja correta. É por isso que todos podem editar respostas.
Miles Rout
18

Três razões para preferir o switch:

  • Um compilador direcionado para código nativo geralmente pode compilar uma instrução switch em uma ramificação condicional mais um salto indireto, enquanto uma sequência de ifs requer uma sequência de ramificações condicionais . Dependendo da densidade dos casos, muitos artigos aprendidos foram escritos sobre como compilar declarações de casos com eficiência; alguns estão vinculados 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 sintaxe do switch torna esse controle mais transparente para o programador do que um ninho de instruções if-then-else.

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

Uma anedota: em uma palestra que ele deu ao receber um prêmio pela conquista da vida, ouvi Tony Hoare dizer que, de todas as coisas que ele fez em sua carreira, havia três das quais ele mais se orgulhava:

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

Não consigo imaginar viver semswitch .

Norman Ramsey
fonte
16

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

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

Amigos não deixam amigos empilhar declarações if-else.


fonte
13
"Amigos não deixam amigos empilhar declarações if-else". Você deve fazer uma sitcker bumber :)
Matthew M. Osborn
14

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

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

tem que ser

switch(variable)
{
  case CONSTANT_VALUE;
  break;
  default:
  break;
}
kemiller2002
fonte
1
você tem algum tipo de número? Estou curioso s para o quão bem um compilador poderia otimizar um comunicado swtich sobre um if / else
Matthew M. Osborn
sim, acredito que uma instrução switch sempre otimiza para O (1) onde uma instrução if else será O (n) onde n é a posição do valor correto nas instruções if / else if.
kemiller2002
No caso de C #, isso não é verdade. Consulte meu post abaixo para obter mais informações.
Scott Wisniewski
Não tenho muita certeza disso, mas não consigo encontrar informações no livro em que juro que as encontrei. Tem certeza de que não está visualizando o código MSIL sem otimização. Não criará a tabela de salto, a menos que você compile com a otimização ativada.
kemiller2002
Compilei no modo de depuração e varejo e, nos dois casos, gera blocos if / else. Você tem certeza de que o livro que estava vendo estava falando sobre C #? O livro foi provavelmente quer um livro compilador ou um livro sobre C ou C ++
Scott Wisniewski
14

Não vi ninguém mais levantar o ponto (óbvio?) De que a suposta vantagem de eficiência da instrução switch depende dos vários casos serem aproximadamente igualmente prováveis. Nos casos em que um (ou alguns) dos valores são muito mais prováveis, a escada 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" pode ser duas vezes mais rápido que o código baseado em comutador. Mesmo se o compilador transformar o "switch" em algum tipo inteligente de controle de tabela, ele ainda não será tão rápido quanto simplesmente verificar zero.

Mark Bessey
fonte
3
Sem otimização prematura! Em geral, se você tiver mais do que apenas alguns casos e eles forem switchcompatíveis, a switchdeclaração será melhor (mais legível, às vezes mais rápida). Se você sabe que um caso é muito mais provável, pode retirá-lo para formar uma construção if- else- switche, se for mensurável , é mais rápido . Deixe isso para dentro. (Repita, se necessário.) Se a switchdegeneração for muito pequena, uma substituição de regex fará a maior parte do trabalho de transformá-la em uma else ifcadeia.
ninguém
6
A pergunta original (há três anos!) Acabou de pedir vantagens e desvantagens entre if / else e switch. Este é um exemplo. Eu pessoalmente vi esse tipo de otimização fazer uma diferença significativa no tempo de execução de uma rotina.
Mark Bessey
7

freqüentemente 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 visualização do código é a diferença mais importante.

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

gbjbaanb
fonte
4

Tópico secundário, mas muitas vezes me preocupo com (e mais frequentemente vejo) if/ elsee a switchdeclaração ficar muito grande com muitos casos. Isso geralmente prejudica a manutenção.

Os culpados comuns incluem:

  1. Fazendo muito dentro de várias instruções if
  2. Mais declarações de casos do que humanamente possível analisar
  3. Demasiadas condições na avaliação if para saber o que está sendo procurado

Consertar:

  1. Extrair para refatoração de método.
  2. Use um dicionário com ponteiros de método em vez de um caso, ou use um IoC para maior configuração. Fábricas de métodos também podem ser úteis.
  3. Extrair as condições para seu próprio método
Chris Brandsma
fonte
4

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, tempo gasto pela instrução Switch = 43.0s e pela instrução If = 48.0s

Que é literalmente 20833333 iterações por segundo. Portanto, se realmente precisarmos nos concentrar mais,

PS: Apenas para saber a diferença de desempenho para uma pequena lista de condições.

Bretfort
fonte
Isso diz tudo para mim.
Greg Gum
3

Se você está apenas usando a instrução if or else, a solução base está usando a comparação? 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;
}
Robert W.
fonte
2

Na verdade, isso não responde à sua pergunta, mas, como haverá pouca diferença entre as versões compiladas, sugiro que você escreva seu código da maneira que melhor descreva suas intenções. Não apenas existe uma chance melhor do compilador fazer o que você espera, como também facilitará a manutenção de seu código por outras pessoas.

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

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

Eu concederei que cody está certo sobre as pessoas esquecerem o comando break, mas quase sempre vejo pessoas fazendo blocos complicados se eles errarem o {}, então as linhas que deveriam estar na declaração condicional não. Essa é uma das razões pelas quais sempre incluo {} nas minhas declarações if, mesmo que haja uma linha nela. Não é apenas mais fácil de ler, mas se eu precisar adicionar outra linha na condicional, não posso esquecer de adicioná-la.

dj_segfault
fonte
2

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 (o refletor é incrível !! eu adoro).

Foi o que descobrimos: Uma instrução switch válida para qualquer coisa que não seja uma string é compilada para IL como uma instrução switch. No entanto, se for uma string, ele será reescrito como um if / else if / else em IL. Portanto, no nosso caso, queríamos saber como as instruções do switch comparam as strings, por exemplo, diferencia maiúsculas de minúsculas etc. e 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 seqüências de caracteres, poderá usar uma instrução switch, pois é mais rápida do que executar uma String.Compare em um if / else. (Edit: Read O que é mais rápido, ative a string ou o tipo if ?, 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, pois o código resultante não é bonito.

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

A melhor regra geral é usar as opções de chave se isso fizer sentido (sério), por exemplo:

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

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

Uma atualização:

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


Uma pequena nota lateral:

Para melhorar a legibilidade das instruções de opção, tente o seguinte:

  • coloque o ramo mais provável em primeiro lugar, ou seja, o mais acessado
  • se é provável que ocorram, liste-os em ordem alfabética, para que seja mais fácil encontrá-los.
  • nunca use o catch-all padrão para a última condição restante, isso é preguiçoso e causará problemas mais tarde na vida do código.
  • use o catch-all padrão para afirmar uma condição desconhecida, mesmo que seja altamente improvável que isso ocorra. é para isso que serve a afirmação.
Dennis
fonte
Em muitos casos, usar ToLower () é a solução certa, especialmente se houver muitos casos e a tabela de hash for gerada.
Blaisorblade 16/01/09
"Se você precisar manipular o valor para alimentar a instrução switch (crie uma variável temporária para a qual alternar), provavelmente deverá usar uma instrução de controle if / else." - Bom conselho, obrigado.
Sneakyness
2

A declaração switch é definitivamente a mais rápida e a mais se. Existem testes de velocidade fornecidos pelo BlackWasp

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

--Confira

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

sleath
fonte
1

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

Não é o caso que o otimizador possa criar o mesmo código. Considere, por exemplo,

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

Como esses são booleanos compostos, o código gerado precisa calcular um valor e fazer um 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á dizendo implicitamente ao compilador que ele não precisa calcular os testes OR e igualdade.

Charlie Martin
fonte
Não há razão para que um bom otimizador não possa lidar com o 1º código, desde que o otim seja implementado. "O compilador não pode otimizar" depende apenas de diferenças semânticas que apenas o ser humano pode reconciliar (ou seja, se f () for chamado, ele não sabe que f () sempre retorna 0 ou 1).
Blaisorblade 16/01/09
0

Meu professor de cs sugeriu que você não alternasse as declarações, pois muitas vezes as pessoas esqueciam o intervalo ou o usavam incorretamente. Não consigo lembrar exatamente o que ele disse, mas algo parecido com o fato de que olhar para alguma base de código seminal que mostrava exemplos da declaração de troca (anos atrás) também apresentava muitos erros.


fonte
Não é realmente um problema em c #. Veja: stackoverflow.com/questions/174155/... ... e também ler stackoverflow.com/questions/188461/... para uma discussão sobre por que viver no medo pode não ser a melhor política ...
Shog9
0

Algo que acabei de notar é que você pode combinar instruções if / else e switch! Muito útil quando é necessário verificar as pré-condições.

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;
}
Even Mien
fonte
3
Eu sei que isso é antigo, mas acho que tecnicamente você não está "combinando" nada. Sempre que você tiver um "else" sem os colchetes, a próxima instrução será executada. Essa declaração pode ser uma declaração de uma linha, normalmente mostrada recuada na próxima linha, ou declarações compostas, como é o caso de if, switch, using, lock, etc. Em outras palavras, você pode ter "else if", " else switch "," else using ", etc. Dito isso, eu gosto de como parece e quase parece intencional. (Disclaimer: Eu não tentei tudo isso para que eu possa estar errado!)
Nelson Rothermel
Nelson, você está 100% correto. Eu descobri por que isso acontece depois que eu publiquei esta resposta.
Até Mien
0

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

Escreva um programa para inserir qualquer número (entre 1 - 99) e verifique se está no slot a) 1 - 9 e depois no slot b) 11 - 19 no slot 2 c) 21-29, no slot três e assim por diante até 89- 99

Depois, se você tiver que criar várias condições, mas caso de troca por filho, basta digitar

Interruptor (no / 10)

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

vai ser tão fácil

Existem muitos outros exemplos também!

Comunidade
fonte
0

uma instrução switch é basicamente uma comparação para igualdade. O evento do teclado tem uma grande vantagem sobre a instrução switch quando é fácil escrever e ler código, e uma instrução if elseif teria.

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

Uma instrução if elseif é ótima para mais de uma solução se (theAmountOfApples for maior que 5 && theAmountOfApples for menor que 10) salve suas maçãs se (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 linguagens próximas.

Geen
fonte
0

Uma possível desvantagem das declarações de switch é a falta de várias condições. Você pode ter várias condições para as instruções if (else), mas não vários casos 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 nas instruções If, mas a legibilidade pode sofrer se a instrução If se tornar difícil de manejar ou for mal administrada.

Ambos têm lugar, dependendo do contexto do que você se depara.

Neil Meyer
fonte