web-dev-qa-db-fra.com

Portée de la variable C #: 'x' ne peut pas être déclaré dans cette étendue car cela donnerait une signification différente à 'x'

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 étendue car elle donnerait une signification différente à 'var', qui est déjà utilisée dans une étendue 'enfant' pour désigner autre chose.

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

Pourquoi C # est-il incapable de différencier les deux étendues? Le premier champ d'application du CI ne doit-il pas être complètement séparé du reste de la méthode?

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

66
user1144

Le problème ici est en grande partie celui des bonnes pratiques et de la 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 étendues ici. Ce serait cependant beaucoup d'efforts pour peu de gain, comme je le vois.

Considérez que si la déclaration de var dans la portée parent était avant l'instruction if, il y aurait un conflit de dénomination insoluble. Le compilateur ne fait tout simplement pas la différence entre les deux cas suivants. L'analyse est effectuée purement basée sur la portée, et non l'ordre de déclaration/utilisation, comme vous semblez l'attendre.

Théoriquement acceptable (mais toujours invalide en ce qui concerne C #):

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

string var = "New VAR!";

et l'inacceptable (car cela cacherait la variable parent):

string var = "New VAR!";

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

sont tous deux traités exactement de la même manière en termes de variables et d'étendues.

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

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

{
    string var = "New VAR!";
}

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

45
Noldorin

n'est-ce pas tout simplement faux?

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

La spécification stipule:


Pour chaque occurrence d'un identifiant donné en tant que nom simple dans une expression ou un déclarant, dans l'espace de déclaration de variable locale de cette occurrence, chaque autre occurrence du même identifiant en tant que nom simple dans une expression ou un déclarant doit se référer à la même entité. Cette règle garantit que la signification d'un nom est toujours la même dans un bloc donné, un bloc de commutation, une instruction for, foreach ou using ou une fonction anonyme.


Pourquoi C # est-il incapable de différencier les deux étendues?

La question est absurde; évidemment, le compilateur est capable de différencier les deux portées. Si le compilateur n'était pas en mesure de différencier les deux étendues, alors comment l'erreur pourrait-elle être produite ? Le message d'erreur indique qu'il existe deux étendues différentes et que, par conséquent, les étendues ont été différenciées!

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

Non, ça ne devrait pas. La portée (et l'espace de déclaration de variable locale) définie par l'instruction de bloc à la suite de l'instruction conditionnelle est lexicalement une partie du bloc externe qui définit le corps de la méthode. Par conséquent, les règles concernant le contenu du bloc externe s'appliquent au contenu du bloc interne.

Je ne peux pas appeler var depuis l'extérieur du if, donc le message d'erreur est 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, que 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 soit pertinente ici est que vous avez un bloc - le bloc extérieur - dans lequel le même nom simple est utilisé pour désigner deux choses complètement différentes. C # nécessite qu'un nom simple ait une signification dans tout 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. Qu'est-ce qu'une erreur est:

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

parce que maintenant le nom simple "x" signifie deux choses différentes à l'intérieur du corps de M - cela signifie "this.x" et la variable locale "x". C'est déroutant pour les développeurs et les responsables du code lorsque le même nom simple signifie deux choses complètement différentes dans le même bloc , ce qui est illégal.

Nous permettons aux blocs parallèles de contenir le même nom simple utilisé de deux manières différentes; c'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 ne contient pas directement aucune utilisation de " x ", seulement indirectement.

34
Eric Lippert

Ceci est valable en C++, mais une source de nombreux bugs et nuits blanches. Je pense que les gars de C # ont décidé qu'il vaut mieux lancer un avertissement/erreur car c'est, dans la grande majorité des cas, un bug plutôt que quelque chose que le codeur veut réellement.

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

MODIFIER (quelques exemples) -----

En C++, ce qui suit est valide (et peu importe que la déclaration externe soit avant ou après la portée interne, elle sera juste plus intéressante et sujette aux bogues si elle est antérieure).

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 il pourrait être facile de ne pas repérer l'erreur. Le compilateur ne se plaint jamais (pas les anciens, pas sûr des nouvelles versions de C++), et la fonction retourne toujours 0.

Le comportement est clairement un bogue, donc ce serait bien si un programme c ++ - lint ou le compilateur le signalait. 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 de compteur dans les boucles. L'un a dit qu'il appartenait à la portée extérieure et l'autre a dit que non. Un peu ennuyeux de travailler sur du code multiplateforme. Permettez-moi de vous donner encore un autre exemple pour garder mon compte en ligne.

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 non dans GCC. Quoi qu'il en soit, C # a nettoyé quelques choses, ce qui est bien.

11
Mats Fredriksson