La variable déclarée dans la boucle for est une variable locale?

133

J'utilise C # depuis assez longtemps mais je n'ai jamais réalisé ce qui suit:

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

Alors pourquoi ne puis-je pas utiliser la valeur de 'i' en dehors du bloc for si cela ne me permet pas de déclarer une variable avec ce nom?

Je pensais que la variable d'itérateur utilisée par une boucle for n'est valide que dans sa portée.

Jean V
la source
8
Parce que la portée du bloc externe inclut la portée de la boucle for
V4Vendetta
3
Une façon dont je pense (et certains guides de style de code l'exigent, en particulier pour les langages à typage dynamique) est que toutes les variables déclarées dans une portée pourraient être déclarées au début de cette portée, ce qui signifie que votre fonction pourrait être réécriteint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith
2
@ V4Vendetta: C'est plutôt l'inverse. Le bloc interne est une boîte noire du bloc parent.
Sebastian Mach
4
Mis à part les raisons techniques pour lesquelles cela n'est pas possible, pourquoi cela (ou une variante de cela) serait-il jamais une chose sensée à faire?! Il sert évidemment un but différent, alors donnez-lui un nom différent.
Dave
Je ne m'en suis pas rendu compte du tout, mais cela semble être une restriction complètement stupide.
alan2here du

Réponses:

119

La raison pour laquelle vous n'êtes pas autorisé à définir une variable avec le même nom à la fois dans la boucle for et en dehors de la boucle for est que les variables de la portée externe sont valides dans la portée interne. Cela signifie qu'il y aurait deux variables «i» dans la boucle for si cela était autorisé.

Voir: Étendues MSDN

Plus précisément:

La portée d'une variable locale déclarée dans une déclaration de variable locale (section 8.5.1) est le bloc dans lequel la déclaration se produit.

et

La portée d'une variable locale déclarée dans un for-initializer d'une instruction for (section 8.8.3) est le for-initializer, la for-condition, le for-iterator et l'instruction contenue de l'instruction for.

Et aussi: Déclarations de variables locales (Section 8.5.1 de la spécification C #)

Plus précisément:

La portée d'une variable locale déclarée dans une déclaration de variable locale est le bloc dans lequel la déclaration se produit. C'est une erreur de faire référence à une variable locale dans une position textuelle qui précède le déclarateur de variable locale de la variable locale. Dans le cadre d'une variable locale, déclarer une autre variable locale ou constante avec le même nom est une erreur de compilation.

(Soulignez le mien.)

Ce qui signifie que la portée de l' iintérieur de votre boucle for est la boucle for. Alors que la portée de l' iextérieur de votre boucle for est la méthode principale entière plus la boucle for. Cela signifie que vous auriez deux occurrences ià l'intérieur de la boucle qui ne sont pas valides selon ce qui précède.

La raison pour laquelle vous n'êtes pas autorisé à le faire int A = i;est que son utilisation int iest uniquement réservée à la forboucle. Il n'est donc plus accessible en dehors de la forboucle.

Comme vous pouvez le voir, ces deux problèmes sont le résultat de la portée; le premier problème ( int i = 4;) entraînerait deux ivariables dans la forportée de la boucle. Alors que cela int A = i;donnerait accès à une variable hors de portée.

Ce que vous pouvez faire à la place, c'est déclarer iêtre étendu à la méthode entière, puis l'utiliser à la fois dans la méthode et dans la portée de la boucle for. Cela évitera d'enfreindre l'une ou l'autre règle.

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

MODIFIER :

Le compilateur C # pourrait bien sûr être modifié pour permettre à ce code de se compiler de manière tout à fait valide. Après tout cela est valable:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

Mais serait-il vraiment bénéfique pour la lisibilité et la maintenabilité de votre code de pouvoir écrire du code tel que:

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

Pensez au potentiel d'erreur ici, est-ce que le dernier iaffiche 0 ou 4? Maintenant, c'est un tout petit exemple, qui est assez facile à suivre et à suivre, mais il est certainement beaucoup moins maintenable et lisible que d'avoir déclaré l'extérieur isous un nom différent.

NB:

Veuillez noter que les règles de portée de C # diffèrent des règles de portée de C ++ . En C ++, les variables sont uniquement dans la portée de l'endroit où elles sont déclarées jusqu'à la fin du bloc. Ce qui ferait de votre code une construction valide en C ++.

Johannes Kommer
la source
Cela a du sens, je pense que cela devrait faire une erreur sur la ivariable interne ; cela me semble plus évident.
George Duckett
Mais la portée est pour la commande et son {}? Ou cela signifie-t-il son parent {}?
John V
2
Eh bien, si je déclare 'A' APRÈS l'instruction for, elle n'est pas valide dans la boucle for car elle est déclarée plus tard. C'est pourquoi je ne comprends pas pourquoi le même nom ne peut pas être utilisé.
John V
9
Peut-être que cette réponse devrait, par souci d'exhaustivité, souligner que les règles de portée C # sont différentes de celles du C ++ dans ce cas. En C ++, les variables sont uniquement dans la portée de l'endroit où elles sont déclarées jusqu'à la fin du bloc (voir msdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx ).
AAT
2
Notez que Java adopte une approche intermédiaire entre C ++ et C #: comme c'est l'exemple de l'OP serait valide en Java, mais si la idéfinition externe était déplacée avant la boucle for, la idéfinition interne serait marquée comme invalide.
Nicola Musatti
29

La réponse de J.Kommer est correcte: brièvement, il est illégal pour une variable locale d'être déclarée dans un espace de déclaration de variable locale qui chevauche un autre espace de déclaration de variable locale qui a un local du même nom.

Il existe également une règle supplémentaire de C # qui est violée ici. La règle supplémentaire est qu'il est illégal pour un nom simple d'être utilisé pour faire référence à deux entités différentes à l'intérieur de deux espaces de déclaration de variable locale se chevauchant différents. Donc non seulement votre exemple est illégal, mais c'est aussi illégal:

class C
{
    int x;
    void M()
    {
        int y = x;
        if(whatever)
        {
            int x = 123;

Parce que maintenant, le nom simple "x" a été utilisé dans l'espace de déclaration des variables locales de "y" pour signifier deux choses différentes - "this.x" et le "x" local.

Voir http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ pour une analyse plus approfondie de ces problèmes.

Eric Lippert
la source
2
En outre, il est intéressant de noter que votre exemple de code se compilera lorsque vous apporterez le changementint y = this.x;
Phil
4
@Phil: C'est exact. this.xn'est pas un simple nom .
Eric Lippert
13

Il existe un moyen de déclarer et d'utiliser ià l'intérieur de la méthode après la boucle:

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

Vous pouvez le faire en Java (cela peut provenir de C, je ne suis pas sûr). C'est bien sûr un peu brouillon pour le nom d'une variable.

Chris S
la source
Oui, cela provient de C / C ++.
Branko Dimitrijevic
1
Je ne savais pas cela avant. Fonction de langue soignée!
@MathiasLykkegaardLorenzen oui
Chris S
7

Si vous aviez déclaré i avant votre forboucle, pensez-vous qu'il devrait toujours être valide de le déclarer à l'intérieur de la boucle?

Non, car alors la portée des deux se chevaucherait.

Quant à ne pas pouvoir le faire int A=i;, eh bien c'est tout simplement parce qu'il in'existe que dans la forboucle, comme il se doit.

Widor
la source
7

En plus de la réponse de J.Kommer (+1 btw). Il y a ceci dans la norme pour la portée NET:

block Si vous déclarez une variable dans une construction de bloc telle qu'une instruction If, la portée de cette variable est seulement jusqu'à la fin du bloc. La durée de vie est jusqu'à la fin de la procédure.

Procédure Si vous déclarez une variable dans une procédure, mais en dehors de toute instruction If, la portée est jusqu'à la fonction End Sub ou End. La durée de vie de la variable est jusqu'à la fin des procédures.

Ainsi, l'int i décalé dans l'en-tête de la boucle for ne sera dans la portée que pendant le bloc de la boucle for, MAIS sa durée de vie dure jusqu'à ce que le Main()code se termine.

ChrisBD
la source
5

La façon la plus simple d'y penser est de déplacer la déclaration externe de I au-dessus de la boucle. Cela devrait alors devenir évident.

C'est la même portée de toute façon, donc ne peut pas être fait.

Andrew Barber
la source
4

De plus, les règles de C # sont souvent inutiles en termes de programmation, mais sont là pour garder votre code propre et lisible.

par exemple, ils auraient pu faire en sorte que si vous le définissez après la boucle, alors tout va bien, mais quelqu'un qui lit votre code et a raté la ligne de définition peut penser que cela a à voir avec la variable de la boucle.

Gars
la source
2

La réponse de Kommer est techniquement correcte. Permettez-moi de le paraphraser avec une métaphore éclatante de l'écran aveugle.

Il y a un écran aveugle unidirectionnel entre le bloc for et le bloc extérieur englobant de sorte que le code de l'intérieur du bloc for puisse voir le code extérieur mais le code du bloc extérieur ne peut pas voir le code à l'intérieur.

Puisque le code externe ne peut pas voir à l'intérieur, il ne peut pas utiliser quoi que ce soit déclaré à l'intérieur. Mais comme le code dans le bloc for peut voir à la fois l'intérieur et l'extérieur, une variable déclarée aux deux endroits ne peut pas être utilisée sans ambiguïté par son nom.

Donc soit vous ne le voyez pas, soit vous C #!

explorateur
la source
0

Regardez-le de la même manière que si vous pouviez déclarer un intdans un usingbloc:

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

Cependant, puisque intne met pas en œuvre IDisposable, cela ne peut pas être fait. Cela peut cependant aider quelqu'un à visualiser comment une intvariable est placée dans une portée privée.

Une autre façon serait de dire,

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

J'espère que cela aidera à visualiser ce qui se passe.

J'aime vraiment cette fonctionnalité, car déclarer le intde l'intérieur de la forboucle garde le code agréable et serré.

jp2code
la source
WTF? Si vous allez marquer un NEG, ayez les boules pour dire pourquoi.
jp2code
Pas le downvoter et je pense que la comparaison avec usingest tout à fait correcte, bien que la sémantique soit plutôt différente (c'est peut-être pourquoi il a été déclassé). Notez que if (true)c'est redondant. Retirez-le et vous avez un bloc de portée.
Abel