Mudar a instrução declaração em c #?


365

O avanço da declaração de switch é um dos meus principais motivos pessoais para amar switchvs. if/else ifconstruções. Um exemplo está em ordem aqui:

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

As pessoas inteligentes estão se encolhendo porque os string[]s devem ser declarados fora da função: bem, eles são, isso é apenas um exemplo.

O compilador falha com o seguinte erro:

O controle não pode passar de um rótulo de caso ('caso 3:') para outro
O controle não pode passar de um rótulo de caso ('caso 2:') para outro

Por quê? E existe alguma maneira de obter esse tipo de comportamento sem ter três ifsegundos?

Respostas:


648

(Copie / cole uma resposta que forneça em outro lugar )

A queda de switch- cases pode ser conseguida sem um código em case(veja case 0) ou usando os formatos especiais goto case(veja case 1) ou goto default(veja 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;
}

121
Penso que, neste caso em particular, o goto não é considerado prejudicial.
9338 Thomas Owens

78
Porra - eu tenho programado com C # desde os primeiros dias da 1.0, e nunca vi isso até agora. Apenas mostra, você aprende coisas novas todos os dias.
Erik Forbes

37
Está tudo bem, Erik. A única razão pela qual eu soube disso é que sou um nerd da teoria dos compiladores que lê as especificações da ECMA-334 com uma lupa.
6609 Alex Lyman

13
@Dancrumb: No momento em que o recurso foi gravado, o C # ainda não havia adicionado nenhuma palavra-chave "flexível" (como 'yield', 'var', 'from' e 'select'), então elas tinham três opções reais: 1 ) faça do 'fallthrough' uma palavra-chave difícil (você não pode usá-la como um nome de variável), 2) escreva o código necessário para suportar uma palavra-chave tão suave, 3) use palavras-chave já reservadas. O número 1 foi um grande problema para quem portava código; # 2 foi uma tarefa de engenharia bastante grande, pelo que entendi; ea opção que foi com, # 3 teve um benefício de lado: outros devs leitura do código após o fato poderia aprender sobre o recurso a partir do conceito de base de Goto
Alex Lyman

10
Toda essa conversa sobre palavras-chave novas / especiais para uma explicação explícita. Eles não poderiam simplesmente ter usado a palavra-chave 'continue'? Em outras palavras. Quebre o interruptor ou continue para o próximo gabinete (caia).
21413 Tal Tal-Tov

44

O "porquê" é evitar falhas acidentais, pelas quais sou grato. Esta é uma fonte não incomum de erros em C e Java.

A solução alternativa é usar goto, por exemplo

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

O design geral de switch / case é um pouco infeliz em minha opinião. Ele ficou muito próximo de C - existem algumas mudanças úteis que podem ser feitas em termos de escopo etc. Indiscutivelmente, uma opção mais inteligente que poderia fazer a correspondência de padrões etc. seria útil, mas isso é realmente mudar da opção para "verificar uma sequência de condições" - nesse ponto, talvez fosse necessário um nome diferente.


11
Esta é a diferença entre switch e if / elseif em minha mente. Switch é para verificar vários estados de uma única variável, enquanto if / elseif pode ser usado para verificar qualquer número de coisas conectadas, mas não necessariamente uma única ou a mesma variável.
Matthew Scharley 6/10/08

2
Se fosse para evitar uma queda acidental, sinto que um aviso do compilador teria sido melhor. Assim como você tem um, se a sua declaração se tem uma atribuição:if (result = true) { }
Tal Mesmo-Tov

3
@ TalEven-Tov: Os avisos do compilador devem ser realmente para casos em que você quase sempre pode corrigir o código para melhorar. Pessoalmente, prefiro quebras implícitas, portanto, não seria um problema para começar, mas isso é uma questão diferente.
precisa

26

A inovação do switch é historicamente uma das principais fontes de erros nos softwares modernos. O designer do idioma decidiu tornar obrigatório o salto no final do caso, a menos que você esteja padronizando o próximo caso diretamente sem processamento.

switch(value)
{
    case 1:// this is still legal
    case 2:
}

23
Eu também não entendo por que não é "caso 1, 2:"
BCS

11
@ David Pfeffer: Sim, é, e também case 1, 2:nos idiomas que permitem isso. O que nunca entenderei é por que nenhuma linguagem moderna escolheria permitir isso.
BCS

@BCS com a instrução goto, várias opções separadas por vírgula podem ser difíceis de manipular?
penguat 13/09/11

@ pengut: pode ser mais preciso dizer que case 1, 2:é um rótulo único, mas com vários nomes. - FWIW, acho que a maioria das linguagens proibidas não se aplica ao caso especial do índium "consecutivo case labels", mas trata os rótulos de caso como uma anotação na próxima declaração e exige a última declaração antes de uma declaração rotulada com (um ou mais) etiquetas de caso para ser um salto.
BCS

22

Para adicionar as respostas aqui, acho que vale a pena considerar a pergunta oposta em conjunto com isso, viz. por que C permitiu a queda em primeiro lugar?

Qualquer linguagem de programação, é claro, serve a dois objetivos:

  1. Forneça instruções ao computador.
  2. Deixe um registro das intenções do programador.

A criação de qualquer linguagem de programação é, portanto, um equilíbrio entre a melhor forma de atender a esses dois objetivos. Por um lado, mais fácil é se transformar em instruções de computador (sejam códigos de máquina, códigos de código como IL ou instruções interpretadas na execução), mais capazes de que o processo de compilação ou interpretação seja eficiente, confiável e compacto na saída. Levado ao extremo, esse objetivo resulta em apenas escrever códigos de montagem, IL ou até códigos brutos, porque a compilação mais fácil é onde não há compilação.

Por outro lado, quanto mais a linguagem expressa a intenção do programador, e não os meios adotados para esse fim, mais compreensível é o programa, ao escrever e durante a manutenção.

Agora, switchsempre poderia ter sido compilado convertendo-o em uma cadeia equivalente de if-elseblocos ou similar, mas foi projetado para permitir a compilação em um padrão de montagem comum em particular em que se obtém um valor, calcula um deslocamento a partir dele (seja consultando uma tabela indexado por um hash perfeito do valor ou por uma aritmética real no valor *). Vale a pena notar que, atualmente, a compilação de C # às vezes se torna switchequivalente if-elsee às vezes usa uma abordagem de salto baseado em hash (e da mesma forma com C, C ++ e outras linguagens com sintaxe comparável).

Nesse caso, há duas boas razões para permitir falhas:

  1. De qualquer maneira, isso acontece naturalmente: se você criar uma tabela de salto em um conjunto de instruções e um dos lotes anteriores não contiver algum tipo de salto ou retorno, a execução progredirá naturalmente no próximo lote. Permitir falhas era o que "aconteceria" se você transformasse switchC em uma tabela de salto - usando código de máquina.

  2. Os codificadores que escreveram em assembly já estavam acostumados ao equivalente: ao escrever uma tabela de salto manualmente no assembly, eles teriam que considerar se um determinado bloco de código terminaria com um retorno, um salto fora da tabela ou apenas continuaria. para o próximo bloco. Como tal, fazer com que o codificador adicione um explícito breakquando necessário também era "natural" para o codificador.

Na época, portanto, era uma tentativa razoável de equilibrar os dois objetivos de uma linguagem de computador, pois ela se relaciona ao código de máquina produzido e à expressividade do código-fonte.

Quatro décadas depois, porém, as coisas não são exatamente as mesmas, por algumas razões:

  1. Atualmente, os codificadores em C podem ter pouca ou nenhuma experiência em montagem. Codificadores em muitas outras linguagens de estilo C são ainda menos propensos a isso (especialmente Javascript!). Qualquer conceito de "com o que as pessoas estão acostumadas a partir da montagem" não é mais relevante.
  2. Melhorias nas otimizações significam que a probabilidade de switchser transformada if-elseporque foi considerada a abordagem mais eficiente, ou então transformada em uma variante particularmente esotérica da abordagem de salto em mesa é maior. O mapeamento entre as abordagens de nível superior e inferior não é tão forte quanto era antes.
  3. A experiência demonstrou que o fall-through tende a ser o caso minoritário, e não a norma (um estudo do compilador da Sun descobriu que 3% dos switchblocos usavam um fall-through além de vários rótulos no mesmo bloco, e pensava-se que o uso- caso aqui significava que esses 3% eram de fato muito mais altos que o normal). Portanto, a linguagem estudada torna o incomum mais facilmente atendido do que o comum.
  4. A experiência mostrou que o fall-through tende a ser a fonte de problemas, tanto nos casos em que é feito acidentalmente, quanto nos casos em que o fall-through correto é ignorado por alguém que mantém o código. Essa última é uma adição sutil aos bugs associados ao fall-through, porque mesmo se seu código estiver perfeitamente livre de erros, seu fall-through ainda poderá causar problemas.

Em relação aos dois últimos pontos, considere a seguinte citação da edição atual da K&R:

Passar de um caso para outro não é robusto, sendo propenso à desintegração quando o programa é modificado. Com exceção de vários rótulos para uma única computação, as falhas devem ser usadas com moderação e comentadas.

Por uma questão de boa forma, faça uma pausa após o último caso (o padrão aqui), mesmo que seja logicamente desnecessário. Algum dia, quando outro caso for adicionado no final, esse pouco de programação defensiva o salvará.

Portanto, pela boca do cavalo, a queda em C é problemática. É uma boa prática sempre documentar falhas com comentários, que é uma aplicação do princípio geral de que se deve documentar onde se faz algo incomum, porque é isso que fará o exame posterior do código e / ou fará com que seu código pareça tem um bug de novato quando está de fato correto.

E quando você pensa sobre isso, codifique assim:

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

Está adicionando algo para tornar explícito o detalhamento no código, simplesmente não é algo que pode ser detectado (ou cuja ausência pode ser detectada) pelo compilador.

Como tal, o fato de que on tem que ser explícito com fall-through em C # não adiciona nenhuma penalidade às pessoas que escreveram bem em outras linguagens de estilo C de qualquer maneira, pois elas já seriam explícitas em seus fall-throughs. †

Finalmente, o uso gotoaqui já é uma norma do C e de outras linguagens:

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

Nesse tipo de caso em que queremos que um bloco seja incluído no código executado para um valor diferente daquele que leva um ao bloco anterior, já estamos tendo que usá-lo goto. (Obviamente, existem meios e maneiras de evitar isso com condicionais diferentes, mas isso é verdade em quase tudo relacionado a essa pergunta). Como tal, o C # se baseou na maneira já normal de lidar com uma situação em que queremos obter mais de um bloco de código em um switche apenas generalizá-lo para cobrir falhas. Isso também tornou os dois casos mais convenientes e com autodocumentação, pois precisamos adicionar um novo rótulo em C, mas podemos usá-lo casecomo um rótulo em C #. Em C #, podemos nos livrar do below_sixrótulo e usar o goto case 5que é mais claro quanto ao que estamos fazendo. (Também teríamos que adicionarbreakpara o default, que deixei de fora apenas para tornar o código C acima claramente não o código C #).

Em resumo, portanto:

  1. O C # não se relaciona mais à saída não otimizada do compilador, tão diretamente quanto o código C fazia 40 anos atrás (nem o C atualmente), o que torna irrelevante uma das inspirações do fall-through.
  2. O C # permanece compatível com o C não apenas por ter implícito break, para facilitar o aprendizado do idioma por pessoas familiarizadas com idiomas semelhantes e facilitar a portabilidade.
  3. O C # remove uma possível fonte de erros ou código incompreendido que foi bem documentado como causador de problemas nas últimas quatro décadas.
  4. O C # torna as práticas recomendadas existentes com C (queda de documentos) aplicáveis ​​pelo compilador.
  5. O C # torna o caso incomum aquele com código mais explícito, o caso usual aquele com o código que apenas escreve automaticamente.
  6. O C # usa a mesma gotoabordagem para atingir o mesmo bloco de caserótulos diferentes que o usado no C. Ele apenas o generaliza em alguns outros casos.
  7. O C # torna essa gotoabordagem baseada em mais conveniente e mais clara do que em C, permitindo que as caseinstruções funcionem como rótulos.

Em suma, uma decisão de design bastante razoável


* Algumas formas de BASIC permitiriam fazer coisas GOTO (x AND 7) * 50 + 240que, embora frágeis e, portanto, um caso particularmente convincente de proibição goto, servem para mostrar um equivalente em idioma mais alto do tipo de maneira como o código de nível inferior pode dar um salto com base em aritmética sobre um valor, que é muito mais razoável quando é o resultado da compilação, em vez de algo que deve ser mantido manualmente. As implementações do Duff's Device, em particular, se prestam bem ao código de máquina ou IL equivalente, porque cada bloco de instruções geralmente terá o mesmo comprimento sem a necessidade de adição de noppreenchimentos.

† O dispositivo de Duff aparece aqui novamente, como uma exceção razoável. O fato de que, com esses padrões e similares, há uma repetição de operações, serve para tornar o uso do fall-through relativamente claro, mesmo sem um comentário explícito nesse sentido.


17

Você pode 'ir para o rótulo da caixa' http://www.blackwasp.co.uk/CSharpGoto.aspx

A instrução goto é um comando simples que transfere incondicionalmente o controle do programa para outra instrução. O comando é frequentemente criticado por alguns desenvolvedores que defendem sua remoção de todas as linguagens de programação de alto nível, pois pode levar ao código de espaguete . Isso ocorre quando há tantas instruções goto ou instruções de salto semelhantes que dificultam a leitura e a manutenção do código. No entanto, existem programadores que apontam que a instrução goto, quando usada com cuidado, fornece uma solução elegante para alguns problemas ...


8

Eles deixaram de fora esse comportamento por design para evitar quando não era usado pela vontade, mas causava problemas.

Pode ser usado apenas se não houver declaração na parte do caso, como:

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}

4

Eles mudaram o comportamento da instrução switch (de C / Java / C ++) para c #. Eu acho que o raciocínio foi que as pessoas esqueceram a queda e erros foram causados. Um livro que li disse usar goto para simular, mas isso não parece uma boa solução para mim.


2
C # suporta goto, mas não avanço? Uau. E não são apenas esses. C # é a única linguagem que conheço que se comporta dessa maneira.
Matthew Scharley 6/10/08

Eu não gostei exatamente no começo, mas "fall-thru" é realmente uma receita para o desastre (especialmente entre programadores juniores.) Como muitos salientaram, o C # ainda permite o fall-thru para linhas vazias (que é a maioria dos .) "Kenny" postou um link que destaca o uso elegante do Goto no caso do switch.
Pretzel

Não é grande coisa na minha opinião. 99% das vezes eu não quero uma queda e fui queimado por bugs no passado.
Ken

11
"isso não parece uma boa solução para mim" - lamento ouvir isso sobre você, porque goto caseé para isso. Sua vantagem sobre o avanço é que é explícito. O fato de algumas pessoas aqui se oporem goto caseapenas a mostrar que foram doutrinadas contra "ir" sem entender o assunto e são incapazes de pensar por conta própria. Quando Dijkstra escreveu "GOTO Considerado Prejudicial", ele estava abordando idiomas que não tinham outros meios de alterar o fluxo de controle.
Jim Balter

@ JimBalter e quantas pessoas citaram Dijkstra sobre isso citarão Knuth que "a otimização prematura é a raiz de todo mal", embora essa citação tenha sido quando Knuth estava escrevendo explicitamente sobre o quão útil gotopode ser na otimização de código?
Jon Hanna

1

Você pode obter uma queda como c ++ pela palavra-chave 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;
}

9
Se alguém tivesse postado isso dois anos antes!

0

Uma instrução de salto, como uma quebra, é necessária após cada bloco de caso, incluindo o último bloco, seja uma declaração de caso ou uma declaração padrão. Com uma exceção (ao contrário da instrução C ++), o C # não suporta uma queda implícita de um rótulo de caso para outro. A única exceção é se uma instrução de caso não tiver código.

- documentação do switch C # ()


11
Sei que esse comportamento está documentado, quero saber por que é assim e todas as alternativas para obter o comportamento antigo.
Matthew Scharley 6/10/08

0

Após cada instrução de caso, exija instrução break ou goto , mesmo que seja um caso padrão.


2
Se alguém tivesse postado isso dois anos antes!

11
@Poldie foi engraçado da primeira vez ... Shilpa, você não precisa de uma pausa ou de ir para todos os casos, apenas para todos os casos com seu próprio código. Você pode ter vários casos que compartilham código muito bem.
Maverick

0

Apenas uma observação rápida para acrescentar que o compilador para o Xamarin realmente entendeu errado e permite o avanço. Supostamente foi corrigido, mas não foi lançado. Descobri isso em algum código que realmente estava caindo e o compilador não reclamou.


0

switch (Referência de C #) diz

O C # requer o final das seções do comutador, incluindo a final,

Portanto, você também precisará adicionar um break;à sua defaultseção, caso contrário, ainda haverá um erro do compilador.


Thx, isso me ajudou;)
CareTaker22

-1

O C # não suporta falhas com instruções de opção / caso. Não sei por que, mas realmente não há suporte para isso. Ligação


Isto é tão errado. Veja as outras respostas ... o efeito de uma queda implícita pode ser obtido com um explícito goto case . Essa foi uma escolha intencional e inteligente de design pelos designers de C #.
Jim Balter

-11

Você esqueceu de adicionar o "intervalo"; instrução no caso 3. No caso 2, você o escreveu no bloco if. Portanto, tente o seguinte:

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

2
Isso produz resultados muito errados. Deixei as instruções do switch por design. A questão é por que o compilador C # vê isso como um erro quando quase nenhum outro idioma possui essa restrição.
Matthew Scharley 6/10/08

5
Que falha impressionante de compreender. E você teve 5 anos para excluir isso e ainda não o fez? Incompreensível.
Jim Balter
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.