J'écrivais ce code:
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.");
}
}
Et a été surpris de trouver une erreur de compilation:
Une variable locale nommée 'action' est déjà définie dans cette portée
C'était un problème assez facile à résoudre; juste se débarrasser de la seconde a var
fait l'affaire.
Il est évident que les variables déclarées dans des case
blocs ont la portée du parent switch
, mais je suis curieux de savoir pourquoi. Étant donné que C # ne permet pas l' exécution de tomber dans d' autres cas (il exige break
, return
, throw
ou des goto case
déclarations à la fin de chaque case
bloc), il semble tout à fait étrange que cela permettrait des déclarations variables à l' intérieur d' un case
à utiliser ou de conflit avec des variables dans tout autre case
. En d'autres termes, les variables semblent tomber à travers les case
instructions, même si l'exécution ne peut pas. C # prend beaucoup de peine à améliorer la lisibilité en interdisant certaines constructions de langages confus ou faciles à abuser. Mais cela semble lié à la confusion. Considérez les scénarios suivants:
Si devions le changer en ceci:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: return (x => x.Action == action);
Je reçois " Utilisation de la variable locale non affectée 'action' ". Ceci est déroutant parce que dans chaque construction en C # à laquelle je peux penser
var action = ...
initialiserait la variable, mais ici, il la déclare simplement.Si je devais échanger les cas comme ceci:
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);
J'obtiens " Impossible d'utiliser la variable locale 'action' avant qu'elle soit déclarée ". Donc, l'ordre des blocs de cas semble être important ici d'une manière qui n'est pas tout à fait évident - normalement je pourrais les écrire dans l'ordre que je souhaite, mais parce que le
var
doit apparaître dans le premier bloc oùaction
est utilisé, je dois modifier descase
blocs en conséquence.Si devions le changer en ceci:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; goto case BindingType.Inherited;
Ensuite, je ne reçois aucune erreur, mais dans un sens, il semble que la variable se voit attribuer une valeur avant d'être déclarée.
(Bien que je ne puisse penser à aucun moment où vous voudriez réellement faire cela - je ne savais même pas qu'ilgoto case
existait avant aujourd'hui)
Ma question est donc la suivante: pourquoi les concepteurs de C # n’ont-ils pas donné aux case
blocs leur propre portée locale? Y a-t-il des raisons historiques ou techniques à cela?
la source
action
variable avant l'switch
instruction ou mettez chaque cas entre ses accolades pour obtenir un comportement judicieux.switch
du tout - je suis simplement curieux de connaître le raisonnement derrière cette conception.break
n'est pas possible en C #.Réponses:
Je pense qu'une bonne raison est que, dans tous les autres cas, la portée d'une variable locale «normale» est un bloc délimité par des accolades (
{}
). Les variables locales qui ne sont pas normales apparaissent dans une construction spéciale avant une instruction (qui est généralement un bloc), comme unefor
variable de boucle ou une variable déclarée dansusing
.Une autre exception concerne les variables locales dans les expressions de requête LINQ, mais celles-ci sont complètement différentes des déclarations de variables locales normales. Je ne pense donc pas qu’il y ait un risque de confusion.
Pour référence, les règles se trouvent au §3.7 Portées de la spécification C #:
(Bien que je ne sois pas tout à fait sûr de savoir pourquoi le
switch
bloc est explicitement mentionné, puisqu’il n’a pas de syntaxe particulière pour les déclinaisons de variables locales, contrairement à toutes les autres constructions mentionnées.)la source
switches
semble avoir un comportement identique à cet égard (sauf pour exiger des sauts à la fin decase
). Il semble que ce comportement ait été simplement copié à partir de là. Donc, je suppose que la réponse courte est: lescase
instructions ne créent pas de blocs, elles définissent simplement les divisions d'unswitch
bloc et n'ont donc pas de portée par elles-mêmes.Mais c'est le cas. Vous pouvez créer des étendues locales n’importe où en enveloppant des lignes avec
{}
la source
Je citerai Eric Lippert, dont la réponse est assez claire sur le sujet:
Donc, à moins que vous ne connaissiez mieux l'équipe de développeurs C # de 1999 qu'Eric Lippert, vous ne saurez jamais la raison exacte!
la source
L'explication est simple - c'est parce que c'est comme ça en C. Des langages tels que C ++, Java et C # ont copié la syntaxe et la portée de l'instruction switch pour des raisons de familiarité.
(Comme indiqué dans une autre réponse, les développeurs de C # ne disposent pas de documentation sur la manière dont cette décision a été prise concernant les études de cas. Mais le principe non déclaré de la syntaxe C # était que, à moins d’avoir des raisons impérieuses de le faire différemment, ils copiaient Java. )
En C, les déclarations de cas sont similaires à celles de goto-labels. L'instruction switch est vraiment une syntaxe plus agréable pour un goto calculé . Les cas définissent les points d'entrée dans le bloc de commutation. Par défaut, le reste du code sera exécuté, sauf en cas de sortie explicite. Il est donc logique qu'ils utilisent la même portée.
(Plus fondamentalement. Les Goto ne sont pas structurés - ils ne définissent ni ne délimitent des sections de code, ils ne définissent que des points de saut. Par conséquent, une étiquette de goto ne peut pas introduire une portée.)
C # conserve la syntaxe, mais introduit une protection contre le "blocage" en exigeant une sortie après chaque clause de casse (non vide). Mais cela change notre façon de penser du commutateur! Les cas sont maintenant des branches alternatives , comme les branches d'un if-else. Cela signifie que nous nous attendons à ce que chaque branche définisse sa propre portée, comme des clauses if-ou itération.
En bref, les cas ont la même portée, car c’est ce qui se passe en C. Mais cela semble étrange et incohérent en C #, car nous considérons les cas comme des branches alternatives plutôt que comme des cibles.
la source
Une manière simplifiée de voir la portée consiste à examiner la portée par blocs
{}
.Comme
switch
il ne contient aucun bloc, il ne peut pas avoir différentes portées.la source
for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */
? Il n'y a pas de blocs, mais il y a différentes portées.for
déclaration.switch
ne crée pas de blocs pour chaque cas, juste au plus haut niveau. La similitude est que chaque instruction crée un bloc (qui compteswitch
comme une instruction).case
place?case
que ne contient pas de bloc, à moins que vous ne décidiez d'en ajouter éventuellement.for
dure pour la déclaration suivante à moins que vous n'ayez ajouté{}
un bloc, maiscase
dure jusqu'à ce que quelque chose vous oblige à quitter le bloc réel, laswitch
déclaration.