Eu estava escrevendo este código:
private static Expression<Func<Binding, bool>> ToExpression(BindingCriterion criterion)
{
switch (criterion.ChangeAction)
{
case BindingType.Inherited:
var action = (byte)ChangeAction.Inherit;
return (x => x.Action == action);
case BindingType.ExplicitValue:
var action = (byte)ChangeAction.SetValue;
return (x => x.Action == action);
default:
// TODO: Localize errors
throw new InvalidOperationException("Invalid criterion.");
}
}
E ficou surpreso ao encontrar um erro de compilação:
Uma variável local denominada 'ação' já está definida neste escopo
Foi uma questão bastante fácil de resolver; apenas se livrar do segundo var
fez o truque.
Evidentemente, as variáveis declaradas em case
blocos têm o escopo do pai switch
, mas estou curioso para saber por que isso acontece. Dado que C # não permite a execução de cair através de outros casos (que requer break
, return
, throw
, ou goto case
declarações no final de cada case
bloco), parece bastante estranho que permitiria declarações de variáveis dentro de um case
para ser usado ou conflito com as variáveis em qualquer outra case
. Em outras palavras, as variáveis parecem passar por case
instruções, embora a execução não possa. O C # esforça-se ao máximo para promover a legibilidade, proibindo algumas construções de outros idiomas que sejam confusas ou que sejam facilmente abusadas. Mas isso parece apenas causar confusão. Considere os seguintes cenários:
Se fosse alterá-lo para isso:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: return (x => x.Action == action);
Recebo " Uso da variável local não atribuída 'action' ". Isso é confuso, porque em qualquer outra construção em C # que eu possa pensar
var action = ...
inicializaria a variável, mas aqui ela simplesmente a declara.Se eu fosse trocar os casos assim:
case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; return (x => x.Action == action); case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action);
Recebo " Não é possível usar a variável local 'action' antes de ser declarada ". Portanto, a ordem dos blocos de casos parece ser importante aqui de uma maneira que não é totalmente óbvia - normalmente eu poderia escrevê-los em qualquer ordem que desejasse, mas como os
var
itens devem aparecer no primeiro bloco em queaction
é usado, tenho que ajustar oscase
blocos. adequadamente.Se fosse alterá-lo para isso:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; goto case BindingType.Inherited;
Não recebo nenhum erro, mas, em certo sentido, parece que a variável está recebendo um valor antes de ser declarada.
(Embora eu não consiga pensar em nenhum momento em que você realmente queira fazer isso - eu nem sabia quegoto case
existia antes de hoje)
Então, minha pergunta é: por que os designers de C # não deram aos case
blocos seu próprio escopo local? Existem razões históricas ou técnicas para isso?
fonte
action
variável antes daswitch
declaração ou coloque cada caso em seu próprio aparelho, e você obterá um comportamento sensato.switch
- eu só estou curioso sobre o raciocínio por trás desse design.break
não seja possível em C #.Respostas:
Eu acho que uma boa razão é que, em todos os outros casos, o escopo de uma variável local "normal" é um bloco delimitado por chaves (
{}
). As variáveis locais que não são normais aparecem em uma construção especial antes de uma instrução (que geralmente é um bloco), como umafor
variável de loop ou uma variável declarada emusing
.Mais uma exceção são as variáveis locais nas expressões de consulta LINQ, mas essas são completamente diferentes das declarações de variáveis locais normais, então não acho que exista uma chance de confusão lá.
Para referência, as regras estão em §3.7 Escopos da especificação C #:
(Embora eu não esteja completamente certo de por que o
switch
bloco é mencionado explicitamente, pois ele não possui nenhuma sintaxe especial para declinações de variáveis locais, diferentemente de todas as outras construções mencionadas).fonte
switches
pareço se comportar de maneira idêntica a esse respeito (exceto por exigir saltos no final decase
). Parece que esse comportamento foi simplesmente copiado de lá. Então, acho que a resposta curta é -case
declarações não criam blocos, elas simplesmente definem divisões de umswitch
bloco - e, portanto, não têm escopos por si mesmas.Mas sim. Você pode criar escopos locais em qualquer lugar, envolvendo linhas com
{}
fonte
Vou citar Eric Lippert, cuja resposta é bastante clara sobre o assunto:
Portanto, a menos que você seja mais íntimo da equipe de desenvolvedores de C # de 1999 do que Eric Lippert, nunca saberá o motivo exato!
fonte
A explicação é simples - é porque é assim em C. Linguagens como C ++, Java e C # copiaram a sintaxe e o escopo da instrução switch por uma questão de familiaridade.
(Conforme declarado em outra resposta, os desenvolvedores do C # não têm documentação sobre como essa decisão foi tomada. Mas o princípio não declarado da sintaxe do C # era que, a menos que eles tivessem razões convincentes para fazê-lo de maneira diferente, eles copiavam o Java. )
Em C, as instruções de caso são semelhantes aos rótulos de goto. A instrução switch é realmente uma sintaxe melhor para um goto computado . Os casos definem os pontos de entrada no bloco de chaves. Por padrão, o restante do código será executado, a menos que haja uma saída explícita. Portanto, faz sentido que eles usem o mesmo escopo.
(Mais fundamentalmente. Os Goto não são estruturados - eles não definem ou delimitam seções de código, apenas definem pontos de salto. Portanto, um rótulo Goto não pode introduzir um escopo.)
O C # mantém a sintaxe, mas introduz uma proteção contra "falhas", exigindo uma saída após cada cláusula de caso (não vazia). Mas isso muda a forma como pensamos na mudança! Os casos agora são ramificações alternativas , como as ramificações em um if-else. Isso significa que esperamos que cada ramo defina seu próprio escopo, como cláusulas se ou cláusulas de iteração.
Em resumo: os casos compartilham o mesmo escopo, porque é assim que é em C. Mas parece estranho e inconsistente em C #, porque pensamos nos casos como ramificações alternativas e não como alvos.
fonte
Uma maneira simplificada de examinar o escopo é considerar o escopo por blocos
{}
.Como
switch
não contém nenhum bloco, ele não pode ter escopos diferentes.fonte
for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */
? Não há blocos, mas existem escopos diferentes.for
declaração.switch
não cria blocos para cada caso, apenas no nível superior. A semelhança é que cada instrução cria um bloco (contandoswitch
como a instrução).case
?case
não contém um bloco, a menos que você decida adicionar um opcional.for
dura a próxima instrução, a menos que você a adicione{}
, sempre tem um bloco, mascase
dura até que algo faça com que você deixe o bloco real, aswitch
instrução.