66 votes

Définition de la portée des variables en C#

if(true)
{
    string var = "VAR";
}

string var = "New VAR!";

Cela se traduira par :

Erreur 1 Une variable locale nommée 'var ne peut pas être déclarée dans cette portée car cela donnerait une signification différente signification différente à 'var', qui est déjà déjà utilisé dans une portée 'enfant' pour désigner quelque chose d'autre.

Rien de bouleversant vraiment, mais n'est-ce pas tout simplement mal ? Un collègue développeur et moi-même nous demandions si la première déclaration ne devrait pas être dans une portée différente, de sorte que la deuxième déclaration ne puisse pas interférer avec la première.

Pourquoi C# est-il incapable de faire la différence entre les deux scopes ? La première portée IF ne devrait-elle pas être complètement séparée du reste de la méthode ?

Je ne peux pas appeler var depuis l'extérieur du if, le message d'erreur est donc erroné, car le premier var n'a aucune pertinence dans la seconde portée.

45voto

Noldorin Points 67794

Il s'agit ici essentiellement d'une question de bonnes pratiques et de prévention contre les erreurs involontaires. Certes, le compilateur C# pourrait théoriquement être conçu de telle sorte qu'il n'y ait pas de conflit entre les champs d'application ici. Cela représenterait toutefois beaucoup d'efforts pour un gain limité, comme je le vois.

Considérons que si la déclaration de var dans la portée parentale étaient avant l'instruction if, il y aurait un conflit de nom insoluble. Le compilateur ne fait tout simplement pas la différence entre les deux cas suivants. L'analyse est faite uniquement en fonction de la portée et non l'ordre de déclaration/utilisation, comme vous semblez l'attendre.

La solution théoriquement acceptable (mais toujours invalide en ce qui concerne C#) :

if(true)
{
    string var = "VAR";
}

string var = "New VAR!";

et l'inacceptable (puisqu'il s'agirait de cacher la variable parent) :

string var = "New VAR!";

if(true)
{
    string var = "VAR";
}

sont traitées exactement de la même manière en termes de variables et de champs d'application.

Maintenant, y a-t-il une raison réelle dans ce scénario pour laquelle vous ne pouvez pas simplement donner un nom différent à l'une des variables ? Je suppose (j'espère) que vos variables actuelles ne s'appellent pas var Je ne vois donc pas vraiment de problème. Si vous avez toujours l'intention de réutiliser le même nom de variable, mettez-les simplement dans des scopes frères :

if(true)
{
    string var = "VAR";
}

{
    string var = "New VAR!";
}

Cependant, bien que cela soit valable pour le compilateur, cela peut conduire à une certaine confusion lors de la lecture du code, donc je le déconseille dans presque tous les cas.

34voto

Eric Lippert Points 300275

Ce n'est pas tout simplement mal ?

Non, ce n'est pas mal du tout. Il s'agit d'une mise en œuvre correcte de la section 7.5.2.1 de la spécification C#, "Noms simples, significations invariantes dans les blocs".

La spécification indique :


Pour chaque occurrence d'un identifiant donné donné en tant que nom simple dans une ou un déclarateur, dans le espace de déclaration des variables locales de cette occurrence, chaque autre occurrence du même identificateur en tant que nom simple dans une doit faire référence au même entité. Cette règle garantit que la signification d'un nom est toujours la même à l'intérieur d'un bloc donné, d'un bloc de commutation, d'une déclaration for, foreach ou using, ou d'un fonction anonyme.


Pourquoi C# est-il incapable de faire la différence entre les deux scopes ?

La question n'a pas de sens ; il est évident que le compilateur est capable de différencier les deux portées. Si le compilateur n'était pas capable de faire la différence entre les deux champs d'application alors comment l'erreur a-t-elle pu être produite ? Le message d'erreur dit qu'il y a deux champs d'application différents, et donc que les champs d'application ont été différenciés !

Le premier champ d'application de la FI ne devrait-il pas être complètement séparé du reste de la méthode ?

Non, il ne devrait pas. La portée (et l'espace de déclaration des variables locales) définie par l'instruction de bloc à la suite de l'instruction conditionnelle fait lexicalement partie du bloc externe qui définit le corps de la méthode. Par conséquent, les règles relatives au contenu du bloc externe s'appliquent au contenu du bloc interne.

Je ne peux pas appeler var depuis l'extérieur du if, le message d'erreur est donc faux, car le premier var n'a aucune pertinence dans la seconde portée.

C'est complètement faux. Il est spécieux de conclure que, simplement parce que la variable locale n'est plus dans la portée, le bloc externe ne contient pas d'erreur. Le message d'erreur est correct.

L'erreur ici n'a rien à voir avec le fait que la portée d'une variable chevauche la portée d'une autre variable ; la seule chose qui est pertinente ici est que vous avez un bloc - le bloc externe - dans lequel le même nom simple est utilisé pour faire référence à deux choses complètement différentes. C# exige qu'un nom simple ait une seule signification dans le bloc qui l'utilise en premier .

Par exemple :

class C 
{
    int x;
    void M()
    { 
        int x = 123;
    }
}

C'est parfaitement légal ; la portée du x extérieur chevauche la portée du x intérieur, mais ce n'est pas une erreur. Ce qui est une erreur est :

class C 
{
    int x;
    void M()
    { 
        Console.WriteLine(x);
        if (whatever)
        {
            int x = 123;
        }
    }
}

parce que maintenant le simple nom "x" a deux significations différentes dans le corps de M -- il signifie "this.x" et la variable locale "x". Il est déroutant pour les développeurs et les mainteneurs de code que le même nom simple ait deux significations différentes. dans le même bloc donc c'est illégal.

Nous autorisons les blocs parallèles à contenir le même nom simple utilisé de deux manières différentes, ce qui est légal :

class C 
{
    int x;
    void M()
    { 
        if (whatever)
        {
            Console.WriteLine(x);
        }
        if (somethingelse)
        {
            int x = 123;
        }
    }
}

car maintenant le seul bloc qui contient deux utilisations incohérentes de x est le bloc externe, et ce bloc n'est pas directement ne contiennent aucun usage de "x", seulement indirectement .

11voto

Mats Fredriksson Points 7136

C'est valable en C++, mais c'est une source de nombreux bogues et de nuits blanches. Je pense que les gars du C# ont décidé qu'il était préférable de lancer un avertissement/une erreur puisqu'il s'agit, dans la grande majorité des cas, d'un bogue plutôt que de quelque chose que le codeur souhaite réellement.

Ici Il y a une discussion intéressante sur les parties de la spécification d'où provient cette erreur.

EDIT (quelques exemples) -----

En C++, ce qui suit est valable (et cela n'a pas vraiment d'importance si la déclaration extérieure est avant ou après la portée intérieure, elle sera juste plus intéressante et plus sujette aux bogues si elle est avant).

void foo(int a)
{
    int count = 0;
    for(int i = 0; i < a; ++i)
    {
        int count *= i;
    }
    return count;
}

Imaginez maintenant que la fonction soit plus longue de quelques lignes et qu'il soit facile de ne pas repérer l'erreur. Le compilateur ne se plaint jamais (pas dans le passé, je ne suis pas sûr des nouvelles versions de C++), et la fonction renvoie toujours 0.

Le problème est clairement un bogue, il serait bon qu'un programme c++-lint ou le compilateur le signale. S'il ne s'agit pas d'un bogue, il est facile de le contourner en renommant simplement la variable interne.

Pour ajouter l'insulte à la blessure, je me souviens que GCC et VS6 avaient des opinions différentes sur la place de la variable compteur dans les boucles for. L'un disait qu'elle appartenait à la portée externe et l'autre non. Un peu ennuyeux de travailler sur du code multiplateforme. Laissez-moi vous donner un autre exemple pour que mon nombre de lignes reste élevé.

for(int i = 0; i < 1000; ++i)
{
    if(array[i] > 100)
        break;
}

printf("The first very large value in the array exists at %d\n", i);

Ce code fonctionnait dans VS6 IIRC et pas dans GCC. Quoi qu'il en soit, C# a nettoyé certaines choses, ce qui est bien.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X