Why can I declare a child variable with the same name as a variable in the parent scope?
Asked Answered
H

1

27

I wrote some code recently where I unintentionally reused a variable name as a parameter of an action declared within a function that already has a variable of the same name. For example:

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

When I spotted the duplication, I was surprised to see that the code compiled and ran perfectly, which is not behavior I would expect based on what I know about scope in C#. Some quick Googling turned up SO questions that complain that similar code does produce an error, such as Lambda Scope Clarification. (I pasted that sample code into my IDE to see if it would run, just to make sure; it runs perfectly.) Additionally, when I enter the Rename dialog in Visual Studio, the first x is highlighted as a name conflict.

Why does this code work? I'm using C# 8 with Visual Studio 2019.

Homophonous answered 29/1, 2020 at 17:58 Comment(4)
The lambda is moved to a method on a class that is generated by the compiler, and thus the entire x parameter to that method gets moved out of the scope. See sharplab for an example.Amusement
It's likely worth noting here that this will not compile when targetting C# 7.3, so this appears to be exclusive to C#8.Baklava
The code in the linked question also compiles fine in sharplab. This might be a recent change.Amusement
found a dupe (without an answer): #58639977Erinerina
O
30

Why does this code work? I'm using C# 8 with Visual Studio 2019.

You've answered your own question! It's because you're using C# 8.

The rule from C# 1 through 7 was: a simple name cannot be used to mean two different things in the same local scope. (The actual rule was slightly more complex than that but describing how is tedious; see the C# specification for details.)

The intention of this rule was to prevent the sort of situation that you're talking about in your example, where it becomes very easy to be confused about the meaning of the local. In particular, this rule was designed to prevent confusions like:

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

And now we have a situation where inside the body of M, x means both this.x and the local x.

Though well-intentioned, there were a number of problems with this rule:

  • It was not implemented to spec. There were situations where a simple name could be used as, say, both a type and a property, but these were not always flagged as errors because the error detection logic was flawed. (See below)
  • The error messages were confusingly worded, and inconsistently reported. There were multiple different error messages for this situation. They inconsistently identified the offender; that is, sometimes the inner usage would be called out, sometimes the outer, and sometimes it was just confusing.

I made an effort in the Roslyn rewrite to sort this out; I added some new error messages, and made the old ones consistent regarding where the error was reported. However, this effort was too little, too late.

The C# team decided for C# 8 that the whole rule was causing more confusion than it was preventing, and the rule was retired from the language. (Thanks Jonathon Chase for determining when the retirement happened.)

If you are interested to learn the history of this problem and how I attempted to fix it, see these articles I wrote about it:

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/

At the end of part three I noted that there was also an interaction between this feature and the "Color Color" feature -- that is, the feature that allows:

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

Here we have used the simple name Color to refer to both this.Color and the enumerated type Color; according to a strict reading of the specification this should be an error, but in this case the spec was wrong and the intention was to allow it, as this code is unambiguous and it would be vexing to make the developer change it.

I never did write that article describing all the weird interactions between these two rules, and it would be a bit pointless to do so now!

Omegaomelet answered 29/1, 2020 at 18:24 Comment(2)
The code in the question fails to compile for C# 6, 7, 7.1, 7.2, and 7.3, giving "CS0136: A local or parameter named 'x' cannot be declared in this scope because that name...". It seems like the rule as still enforced up until C# 8.Baklava
@JonathonChase: Thanks!Omegaomelet

© 2022 - 2024 — McMap. All rights reserved.