Eu tenho uma switch
estrutura que tem vários casos para lidar. O switch
opera sobre um enum
que coloca a questão do código duplicado através de valores combinados:
// All possible combinations of One - Eight.
public enum ExampleEnum {
One,
Two, TwoOne,
Three, ThreeOne, ThreeTwo, ThreeOneTwo,
Four, FourOne, FourTwo, FourThree, FourOneTwo, FourOneThree,
FourTwoThree, FourOneTwoThree
// ETC.
}
Atualmente, a switch
estrutura lida com cada valor separadamente:
// All possible combinations of One - Eight.
switch (enumValue) {
case One: DrawOne; break;
case Two: DrawTwo; break;
case TwoOne:
DrawOne;
DrawTwo;
break;
case Three: DrawThree; break;
...
}
Você entendeu a idéia. Atualmente, eu tenho isso dividido em uma if
estrutura empilhada para manipular combinações com uma única linha:
// All possible combinations of One - Eight.
if (One || TwoOne || ThreeOne || ThreeOneTwo)
DrawOne;
if (Two || TwoOne || ThreeTwo || ThreeOneTwo)
DrawTwo;
if (Three || ThreeOne || ThreeTwo || ThreeOneTwo)
DrawThree;
Isso coloca a questão de avaliações lógicas incrivelmente longas, que são confusas de ler e difíceis de manter. Depois de refatorá-lo, comecei a pensar em alternativas e a pensar em uma switch
estrutura com repercussão entre os casos.
Eu tenho que usar um goto
nesse caso, pois C#
não permite falhas. No entanto, evita cadeias lógicas incrivelmente longas, mesmo que salte pela switch
estrutura e ainda gera duplicação de código.
switch (enumVal) {
case ThreeOneTwo: DrawThree; goto case TwoOne;
case ThreeTwo: DrawThree; goto case Two;
case ThreeOne: DrawThree; goto default;
case TwoOne: DrawTwo; goto default;
case Two: DrawTwo; break;
default: DrawOne; break;
}
Isso ainda não é uma solução suficientemente limpa e há um estigma associado à goto
palavra - chave que eu gostaria de evitar. Tenho certeza de que deve haver uma maneira melhor de limpar isso.
Minha pergunta
Existe uma maneira melhor de lidar com esse caso específico sem afetar a legibilidade e a manutenção?
fonte
goto
quando a estrutura de alto nível não existe no seu idioma. Às vezes eu gostaria que houvesse umfallthru
; palavra-chave para se livrar desse uso específico de,goto
mas tudo bem.goto
em uma linguagem de alto nível como C #, provavelmente ignorou muitas outras (e melhores) alternativas de design e / ou implementação. Altamente desencorajado.Respostas:
Acho o código difícil de ler com as
goto
instruções. Eu recomendaria estruturar o seu de maneiraenum
diferente. Por exemplo, se vocêenum
fosse um campo de bits em que cada bit representasse uma das opções, poderia ser assim:O atributo Flags indica ao compilador que você está configurando valores que não se sobrepõem. O código que chama esse código pode definir o bit apropriado. Você pode fazer algo assim para deixar claro o que está acontecendo:
Isso requer o código configurado
myEnum
para definir os campos de bits corretamente e marcado com o atributo Flags. Mas você pode fazer isso alterando os valores das enumerações no seu exemplo para:Ao escrever um número no formulário
0bxxxx
, você o especifica em binário. Assim, você pode ver que definimos o bit 1, 2 ou 3 (bem, tecnicamente 0, 1 ou 2, mas você entendeu). Você também pode nomear combinações usando um OR bit a bit, se as combinações puderem ser frequentemente definidas juntas.fonte
public enum ExampleEnum { One = 1 << 0, Two = 1 << 1, Three = 1 << 2, OneAndTwo = One | Two, OneAndThree = One | Three, TwoAndThree = Two | Three };
. Não há necessidade de insistir no C # 7 +.[Flags]
atributo não sinaliza nada para o compilador. É por isso que você ainda tem que declarar explicitamente os valores enum como potências de 2.HasFlag
é um termo descritivo e explícito para a operação que está sendo executada e abstrai a implementação da funcionalidade. Usar&
porque é comum em outros idiomas não faz mais sentido do que usar umbyte
tipo em vez de declarar umenum
.Na OMI, a raiz do problema é que esse pedaço de código nem deveria existir.
Aparentemente, você tem três condições independentes e três ações independentes a serem executadas se essas condições forem verdadeiras. Então, por que tudo isso está sendo canalizado em um pedaço de código que precisa de três sinalizadores booleanos para dizer o que fazer (se você os obscurece ou não em uma enumeração) e faz alguma combinação de três coisas independentes? O princípio da responsabilidade única parece ter um dia de folga aqui.
Faça as chamadas para as três funções em que pertencem (ou seja, onde você descobre a necessidade de executar as ações) e consigne o código nesses exemplos à lixeira.
Se houvesse dez sinalizadores e ações e não três, você estenderia esse tipo de código para lidar com 1024 combinações diferentes? Espero que não! Se 1024 é demais, 8 também é demais, pelo mesmo motivo.
fonte
Nunca use gotos é um dos conceitos de "mentiras para crianças" da ciência da computação. É o conselho certo 99% do tempo, e os horários em que não são tão raros e especializados que são muito melhores para todos se forem explicados aos novos codificadores como "não os use".
Então, quando eles devem ser usados? Existem alguns cenários , mas o básico que você parece estar enfrentando é: quando você está codificando uma máquina de estado . Se não há expressão melhor organizada e estruturada do seu algoritmo do que uma máquina de estado, sua expressão natural no código envolve ramificações não estruturadas, e não há muito que possa ser feito sobre o que não pode ser discutido na estrutura do máquina de estado em si mais obscura, em vez de menos.
Os escritores de compiladores sabem disso, e é por isso que o código fonte da maioria dos compiladores que implementam analisadores LALR * contém gotos. No entanto, poucas pessoas realmente codificam seus próprios analisadores e analisadores lexicais.
* - No IIRC, é possível implementar gramáticas LALL de descida totalmente recursiva sem recorrer a tabelas de salto ou outras instruções de controle não estruturadas; portanto, se você é realmente anti-goto, essa é uma saída.
Agora, a próxima pergunta é: "Este exemplo é um desses casos?"
O que estou vendo é que você tem três próximos estados possíveis possíveis, dependendo do processamento do estado atual. Como um deles ("padrão") é apenas uma linha de código, tecnicamente você pode se livrar dele aderindo a essa linha de código no final dos estados aos quais se aplica. Isso reduziria você a 2 possíveis próximos estados.
Um dos restantes ("Três") é ramificado apenas de um lugar que eu posso ver. Então você pode se livrar da mesma maneira. Você terminaria com um código assim:
No entanto, novamente este foi um exemplo de brinquedo que você forneceu. Nos casos em que "padrão" possui uma quantidade não trivial de código, "três" é transferido para vários estados ou (o mais importante) é provável que uma manutenção adicional inclua ou complique os estados , seria melhor você honestamente usando gotos (e talvez até se livrando da estrutura enum-case que oculta a natureza das máquinas de estado das coisas, a menos que haja uma boa razão para que ela permaneça).
fonte
goto
.A melhor resposta é usar polimorfismo .
Outra resposta, que, na IMO, torna as coisas mais claras e sem dúvida mais curtas :
goto
é provavelmente a minha 58ª escolha aqui ...fonte
Por que não isso:
OK, eu concordo, isso é uma tolice (eu não sou um desenvolvedor de C #, desculpe-me pelo código), mas do ponto de vista da eficiência, isso é necessário? Usar enumerações como índice de matriz é C # válido.
fonte
int[ExempleEnum.length] COUNTS = { 1, 3, 4, 2, 5, 3 };
:?Se você não pode ou não deseja usar sinalizadores, use uma função recursiva de cauda. No modo de liberação de 64 bits, o compilador produzirá um código muito semelhante à sua
goto
declaração. Você simplesmente não precisa lidar com isso.fonte
A solução aceita é boa e é uma solução concreta para o seu problema. No entanto, gostaria de propor uma solução alternativa e mais abstrata.
Na minha experiência, o uso de enums para definir o fluxo da lógica é um cheiro de código, pois geralmente é um sinal de design de classe ruim.
Encontrei um exemplo real disso acontecendo no código em que trabalhei no ano passado. O desenvolvedor original criou uma classe única que importava e exportava a lógica e alternava entre as duas com base em uma enumeração. Agora, o código era semelhante e tinha algum código duplicado, mas era diferente o suficiente para tornar o código significativamente mais difícil de ler e praticamente impossível de testar. Acabei refatorando isso em duas classes separadas, o que simplificou as duas e, na verdade, me permitiu identificar e eliminar uma série de bugs não relatados.
Mais uma vez, devo declarar que o uso de enums para controlar o fluxo da lógica geralmente é um problema de design. No caso geral, as enums devem ser usadas principalmente para fornecer valores de tipo seguro e amigáveis ao consumidor, onde os valores possíveis estão claramente definidos. Eles são mais usados como uma propriedade (por exemplo, como um ID de coluna em uma tabela) do que como um mecanismo de controle lógico.
Vamos considerar o problema apresentado na pergunta. Eu realmente não sei o contexto aqui, ou o que esse enum representa. É desenhar cartas? Desenhando imagens? Desenhando sangue? A ordem é importante? Eu também não sei o quão importante é o desempenho. Se o desempenho ou a memória forem críticos, provavelmente esta solução não será a desejada.
De qualquer forma, vamos considerar o enum:
O que temos aqui são vários valores diferentes de enumerações que representam diferentes conceitos de negócios.
Em vez disso, o que poderíamos usar são abstrações para simplificar as coisas.
Vamos considerar a seguinte interface:
Podemos então implementar isso como uma classe abstrata:
Podemos ter uma classe concreta para representar o desenho um, dois e três (que, por razões de argumento, têm uma lógica diferente). Eles poderiam usar a classe base definida acima, mas suponho que o conceito DrawOne seja diferente do conceito representado pela enumeração:
E agora temos três classes separadas que podem ser compostas para fornecer a lógica para as outras classes.
Essa abordagem é muito mais detalhada. Mas tem vantagens.
Considere a seguinte classe, que contém um erro:
Quão mais simples é identificar a chamada drawThree.Draw () ausente? E se a ordem é importante, a ordem das chamadas também é muito fácil de ver e seguir.
Desvantagens para esta abordagem:
Vantagens dessa abordagem:
Considere essa abordagem (ou similar) sempre que sentir a necessidade de ter qualquer código de controle lógico complexo escrito em instruções de caso. Futuro, você será feliz que você fez.
fonte
Se você pretende usar uma opção aqui, seu código será realmente mais rápido se você lidar com cada caso separadamente
apenas uma única operação aritmética é executada em cada caso
Além disso, como outros já afirmaram, se você está pensando em usar o gotos, provavelmente deve repensar seu algoritmo (embora eu admita que a falta de casos em c # s possa ser um motivo para usar o goto). Veja o famoso artigo de Edgar Dijkstra "Ir para a declaração considerada prejudicial"
fonte
Para o seu exemplo em particular, uma vez que tudo o que você realmente queria da enumeração era uma Do / Do-not indicador para cada uma das etapas, a solução que reescreve suas três
if
declarações é preferível a umswitch
, e é bom que você fez isso a resposta aceita .Mas se você tivesse uma lógica mais complexa que não funcionasse de maneira tão limpa, ainda acho os
goto
s naswitch
declaração confusos. Prefiro ver algo assim:Isso não é perfeito, mas acho que é melhor assim do que com os
goto
s. Se as seqüências de eventos são tão longas e se duplicam tanto que realmente não faz sentido especificar a sequência completa para cada caso, eu prefiro uma sub-rotina do quegoto
para reduzir a duplicação de código.fonte
O motivo para odiar a
goto
palavra-chave é código comoOpa! Isso claramente não vai funcionar. A
message
variável não está definida nesse caminho de código. Portanto, o C # não passa isso. Mas isso pode estar implícito. ConsiderarE suponha que
display
então tenha aWriteLine
declaração.Esse tipo de bug pode ser difícil de encontrar porque
goto
obscurece o caminho do código.Este é um exemplo simplificado. Suponha que um exemplo real não seria tão óbvio. Pode haver cinquenta linhas de código entre
label:
e o uso demessage
.O idioma pode ajudar a corrigir isso, limitando como
goto
pode ser usado, descendo apenas dos blocos. Mas o C #goto
não é limitado assim. Pode pular o código. Além disso, se você pretende limitargoto
, é melhor mudar o nome. Outros idiomas são usadosbreak
para descer dos blocos, com um número (de blocos para sair) ou um rótulo (em um dos blocos).O conceito de
goto
é uma instrução de linguagem de máquina de baixo nível. Mas toda a razão pela qual temos linguagens de nível superior é para nos limitarmos às abstrações de nível superior, por exemplo, escopo variável.Tudo isso dito, se você usar o C #
goto
dentro de umaswitch
instrução para ir de um caso para outro, é razoavelmente inofensivo. Cada caso já é um ponto de entrada. Ainda acho que chamá-lo degoto
bobo nessa situação, pois confunde esse uso inofensivogoto
com formas mais perigosas. Eu preferiria que eles usassem algo assimcontinue
para isso. Mas, por alguma razão, eles se esqueceram de me perguntar antes de escreverem o idioma.fonte
C#
idioma, você não pode pular sobre declarações de variáveis. Para prova, inicie um aplicativo de console e insira o código que você forneceu em sua postagem. Você receberá um erro de compilador no seu rótulo informando que o erro é o uso da variável local não atribuída 'message' . Nos idiomas em que isso é permitido, é uma preocupação válida, mas não no idioma C #.Quando você tem tantas opções (e ainda mais, como você diz), talvez não seja código, mas dados.
Crie um dicionário de mapeamento de valores de enumeração para ações, expresso como funções ou como um tipo de enumeração mais simples, representando as ações. Em seguida, seu código pode ser resumido em uma simples pesquisa de dicionário, seguida pela chamada do valor da função ou pela alteração das opções simplificadas.
fonte
Use um loop e a diferença entre interromper e continuar.
fonte