Pourquoi puis-je déclarer une variable enfant avec le même nom qu'une variable dans la portée parent?

23

J'ai récemment écrit du code où j'ai involontairement réutilisé un nom de variable comme paramètre d'une action déclarée dans une fonction qui a déjà une variable du même nom. Par exemple:

var x = 1;
Action<int> myAction = (x) => { Console.WriteLine(x); };

Lorsque j'ai repéré la duplication, j'ai été surpris de voir que le code était compilé et fonctionnait parfaitement, ce qui n'est pas un comportement que j'attendrais en fonction de ce que je sais de la portée en C #. Googling rapide relevai SO questions qui se plaignent que le code similaire ne génère une erreur, comme Scope Lambda clarification . (J'ai collé cet exemple de code dans mon IDE pour voir s'il fonctionnerait, juste pour être sûr; il fonctionne parfaitement.) De plus, lorsque j'entre dans la boîte de dialogue Renommer dans Visual Studio, le premier xest mis en surbrillance en tant que conflit de nom.

Pourquoi ce code fonctionne-t-il? J'utilise C # 8 avec Visual Studio 2019.

stellr42
la source
1
Le lambda est déplacé vers une méthode sur une classe qui est générée par le compilateur, et donc le xparamètre entier de cette méthode est déplacé hors de la portée. Voir sharplab pour un exemple.
Lasse V. Karlsen
6
Il convient probablement de noter ici que cela ne se compilera pas lors du ciblage de C # 7.3, donc cela semble être exclusif à C # 8.
Jonathon Chase
Le code dans la question liée compile également très bien dans sharplab . Cela pourrait être un changement récent.
Lasse V. Karlsen
2
trouvé une dupe (sans réponse): stackoverflow.com/questions/58639477/…
bolov

Réponses:

26

Pourquoi ce code fonctionne-t-il? J'utilise C # 8 avec Visual Studio 2019.

Vous avez répondu à votre propre question! C'est parce que vous utilisez C # 8.

La règle de C # 1 à 7 était: un nom simple ne peut pas être utilisé pour signifier deux choses différentes dans la même portée locale. (La règle réelle était légèrement plus complexe que cela, mais décrivant comment cela est fastidieux; voir la spécification C # pour plus de détails.)

L'intention de cette règle était d'empêcher le genre de situation dont vous parlez dans votre exemple, où il devient très facile de se tromper sur le sens du local. En particulier, cette règle a été conçue pour éviter les confusions comme:

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

Et maintenant, nous avons une situation où à l'intérieur du corps de M, xsignifie à la fois this.xet le local x.

Bien que bien intentionnée, cette règle posait un certain nombre de problèmes:

  • Il n'a pas été mis en œuvre conformément aux spécifications. Dans certains cas, un nom simple pouvait être utilisé, par exemple, comme type et comme propriété, mais ceux-ci n'étaient pas toujours signalés comme des erreurs car la logique de détection des erreurs était défectueuse. (Voir ci-dessous)
  • Les messages d'erreur étaient formulés de manière confuse et rapportés de manière incohérente. Il y avait plusieurs messages d'erreur différents pour cette situation. Ils ont identifié le contrevenant de façon incohérente; c'est-à-dire que parfois l' usage intérieur était appelé, parfois l' extérieur , et parfois c'était juste déroutant.

J'ai fait un effort dans la réécriture de Roslyn pour régler ce problème; J'ai ajouté de nouveaux messages d'erreur et rendu les anciens cohérents quant à l'endroit où l'erreur a été signalée. Cependant, cet effort était trop peu, trop tard.

L'équipe C # a décidé pour C # 8 que toute la règle causait plus de confusion qu'elle n'en empêchait, et la règle a été retirée du langage. (Merci Jonathon Chase d'avoir déterminé quand la retraite a eu lieu.)

Si vous êtes intéressé à connaître l'historique de ce problème et comment j'ai essayé de le résoudre, consultez les articles que j'ai écrits à ce sujet:

https://ericlippert.com/2009/11/02/simple-names-are-not-so-simple/

https://ericlippert.com/2009/11/05/simple-names-are-not-so-simple-part-two/

https://ericlippert.com/2014/09/25/confusing-errors-for-a-confusing-feature-part-one/

https://ericlippert.com/2014/09/29/confusing-errors-for-a-confusing-feature-part-two/

https://ericlippert.com/2014/10/03/confusing-errors-for-a-confusing-feature-part-three/

À la fin de la troisième partie, j'ai noté qu'il y avait également une interaction entre cette fonctionnalité et la fonctionnalité "Couleur couleur" - c'est-à-dire la fonctionnalité qui permet:

class C
{
  Color Color { get; set; }
  void M()
  {
    Color = Color.Red;
  }
}

Ici, nous avons utilisé le nom simple Colorpour faire référence à la fois this.Colorau type énuméré Color; selon une lecture stricte de la spécification, cela devrait être une erreur, mais dans ce cas, la spécification était erronée et l'intention était de l'autoriser, car ce code est sans ambiguïté et il serait vexant de faire changer le développeur.

Je n'ai jamais écrit cet article décrivant toutes les interactions étranges entre ces deux règles, et il serait un peu inutile de le faire maintenant!

Eric Lippert
la source
Le code dans la question ne parvient pas à compiler pour C # 6, 7, 7.1, 7.2 et 7.3, ce qui donne "CS0136: Un local ou un paramètre nommé 'x' ne peut pas être déclaré dans cette étendue car ce nom ...". Il semble que la règle soit toujours appliquée jusqu'au C # 8.
Jonathon Chase
@JonathonChase: Merci!
Eric Lippert