3

I am writing a Blazor component and noticed something unexpected. In my Razor code, I use a foreach to loop over a list, and inside the Click event handler of a RadzenButton I declare another foreach with the same loop variable name. I thought this would cause a compiler error due to a duplicate variable in scope, but the code compiles and runs fine.

@foreach(var someItem in SomeStringList)
{
    <p>@someItem</p>
    <RadzenButton Click="@(() => {
        Console.WriteLine(someItem);
        foreach(var someItem in SomeOtherStringList)
        {
            Console.WriteLine(someItem);
        }
    })">
    </RadzenButton>
}

@code {
    public List<string> SomeStringList = new() { "Item 1", "Item 2", "Item 3" };
    public List<string> SomeOtherStringList = new() { "Item A", "Item B", "Item C" };
}

This compiles and runs fine. But I would have expected a compiler error because I’m redeclaring the variable someItem inside the nested foreach, while outer someItem is still in scope (and even being used in the lambda for the Click handler).

My questions:

  • Why does this compile without a conflict?
  • How does C# handle the scope of the two someItem variables in this case?
  • Is this safe to rely on, or should I always avoid reusing variable names like this?

I tried using the same variable name someItem in both the outer and inner foreach loops. I expected the compiler to complain about a name conflict, since the outer variable is still being referenced inside the lambda expression. However, the code compiled without any error and both loops work. I want to understand why C# allows this and whether it is safe or just shadowing the variable.

3
  • 2
    I think this is a radzen compiler bug... I recommend to create a post on forum.radzen.com Commented Sep 2 at 6:29
  • 4
    @Joe I just tested it without Radzen. It still works. You can add the above snippet to an empty Blazor project (change the button to <button type="button" onclick="@(() => { ...) and the behaviour is as in this questoin Commented Sep 2 at 6:38
  • @Joe - does Radzen have a compiler? Commented Sep 2 at 13:34

4 Answers 4

3

Why does this compile without a conflict?

Because this is a Razor file, not a C# code file. The Razor compiler produces a very different C# file from what you see.

To enable outputting the C# intermediate file generated by the Razor compiler, add this line to the project file:

  <PropertyGroup>
      <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  </PropertyGroup>

How does C# handle the scope of the two someItem variables in this case?

Go find the files in ./obj/Debug/netxxx/generated.

You'll then see why it works.

Is this safe to rely on, or should I always avoid reusing variable names like this?

I think you already know the answer. I think it's bad coding practice.

This is a good example of why you should separate out C# code from markup:

<button type="button" onclick="() => this.ButtonHandler(value)"  ..>

@code {

private Task ButtonHandler(string value)
//...

}
Sign up to request clarification or add additional context in comments.

4 Comments

"Go find the files in ./obj/Debug/netxxx/generated" but could you summarise that so that I don't have to? Otherwise you haven't quite answered the question.
I know. But I don't know the directory structure, the razor file or the version of Net, so how can I provide the exact path in the answer. And without the full file [there's only a snippet here] I can't show what the compiler produced. Which is why I answered the question in the way I have.
I mean that you could copy paste snippets from a generated class in order to answer the question. Otherwise you haven't really explained why the scope of the two variables is different.
2

Well, what you have done is you defined event handler in separate method.

Translating this to C#, you could look at this inline definition as totally separate code. I.e. inline code is rather executing when the page is rendered and when the user interacts with the page (clicks the button).

And C# code is code that is run on server, during page rendering.

If you understand that separation, you would see that those two pieces of code (outer loop and event handler with inner loop) are executed in totally different context/environment.

I understand that it seems strange for you, but you could look at it as if you defined event handler in just separate method and there you have just referenced that method.

But when you will have more experience, it would become natural to think this way. For me it would be surprising if I would have any problems with such code.

Comments

2

Short version: () => { ... } is an inline method. So it is a new scope and the rules are the same as when it had been a normal member method.

The difference is that because it's a lambda it could have captured someItem from the surrounding code but because of a priority rule it doesn't. It is the same situation as:

double i = 3.14;
void M() 
{
  // double i here
  for(int i = 0; i < 5; i++) { ... }  // int i here
  // double i again
} 

The is don't bite.

Comments

1

If you had plain nested loops in your code both loop variables would be locals in the same method body. This doesn't work and will result in a CS0136 compiler error.

However in your case you have a foreach loop and a second one which is in a closure. This means that the someItem variable of the outer loop is compiled into a closure field while the inner someItem loop-variable is compiled into a local variable. Since they are different kinds of symbols there is no conflict.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.