Skip to content
This repository was archived by the owner on Apr 20, 2019. It is now read-only.

Explain the difference between for and foreach loop in context of lambda expression #371

Closed
Andrzej-W opened this issue Jan 12, 2019 · 4 comments

Comments

@Andrzej-W
Copy link
Contributor

Andrzej-W commented Jan 12, 2019

In the current doc we have plenty of examples with foreach loops, but people often use for loops and have problems. Although it is a general C# topic and not Blazor specific, we should add some info and explain the difference. Otherwise we will have more "bug reports" like these: https://github.com/aspnet/Blazor/issues/665, https://github.com/aspnet/Blazor/issues/764, https://github.com/aspnet/Blazor/issues/1402, dotnet/aspnetcore#6591.
Notice that I selected only a few examples and there are a lot of similar issues.

Problem is typically seen in event handlers and binding expressions. We should explain that in for loop we have only one iteration variable and in foreach we have a new variable for every iteration. We should explain that HTML content is rendered when for / foreach loop is executed, but event handlers are called later. Here is an example code to demonstrate one wrong and two good solutions.

<div>Item clicked: @Item List: @List</div>
<br />
<div>List A</div>
@for (var i = 0; i < 3; i++)
{
    // This is wrong. Each <div> element, when clicked, will call ItemClicked
    // with the current value of variable i which is 3 at the end of the loop.
    <div onclick=@(_ => ItemClicked(i, 'A'))>Item @i</div>
}
<br />
<div>List B</div>
@for (var i = 0; i < 3; i++)
{
    int localVar = i;
    <div onclick=@(_ => ItemClicked(localVar, 'B'))>Item @localVar</div>
}
<br />
<div>List C</div>
@foreach (var i in intTable)
{
    <div onclick=@(_ => ItemClicked(i, 'C'))>Item @i</div>
}
@functions{
    int Item { get; set; } = -1;
    char List { get; set; } = '?';
    int[] intTable = { 0, 1, 2 };

    void ItemClicked(int value, char list)
    {
        Item = value;
        List = list;
    }
}

Unfortunately English is not my native language and probably I'm not the proper person to write this.

I'm also not sure where to add this information, maybe here: https://blazor.net/docs/components/index.html#event-handling

@Andrzej-W
Copy link
Contributor Author

Andrzej-W commented Jan 12, 2019

Here is yet another example with binding expressions.
Binding expression in the first for loop doesn't work correctly. Loop has one less iteration to remove the risk of exception (array index out of range). Page is rendered correctly because it happens when for loop is executed. Unfortunately in binding expression we use variable i which will have value equal 3 at the and of the loop. Try to edit any customer in the first list and press TAB key or click outside of the field. Notice that list B and C is refreshed and you edited fourth (last) customer which is not even displayed in the first list.

<div>List A</div>
@for (var i = 0; i < customers.Length - 1; i++)
{
    // This binding expression doesn't work correctly.
    <div><input bind="@customers[i].Name" /></div>
}
<br />
<div>List B</div>
@for (var i = 0; i < customers.Length; i++)
{
    int localVar = i;
    <div><input bind="@customers[localVar].Name" /></div>
}
<br />
<div>List C</div>
@foreach (var cust in customers)
{
    <div><input bind="@cust.Name" /></div>
}
@functions{
    public class Customer
    {
        public string Name { get; set; }
    };

    Customer[] customers = new Customer[]
    {
        new Customer { Name = "Cust A" },
        new Customer { Name = "Cust B" },
        new Customer { Name = "Cust C" },
        new Customer { Name = "Last customer" }
    };
}

Probably the best place to add this example is here https://blazor.net/docs/components/index.html#data-binding a little above event handling.

Eventually here https://blazor.net/docs/tutorials/build-your-first-blazor-app.html#build-a-todo-list in step number 17 we can add warning that if someone wants to use for loop he/she should read section about data binding in components.

@guardrex
Copy link
Collaborator

Thanks for opening this issue @Andrzej-W. Your points and explanation are excellent.

Before we discuss what to do in the Blazor docs, let's call in the TOP GUN in C# for advice ...

@BillWagner 👋 TL;DR Devs are getting tripped up using for with lambdas and binding in Blazor code; consequently, they're opening issues to ask about potentially broken Razor/Blazor API when the real problem is how they're using C#. @Andrzej-W links to some of these issues in his opening post here. ☝️

I'd like to link to C# Guide content (or other authoritative content) and provide as little original content in the Blazor docs as possible on these points. I took a look at ...

I don't see the conceptual difference that we'd like to highlight for this scenario laid out plainly ... not in a form that we can link, such as a specific section that goes directly to these points.

What's your advice, and are you aware of content that we can link in a MS doc set (or perhaps another trusted doc set)?

@Andrzej-W
Copy link
Contributor Author

@guardrex , @BillWagner I have found this "trusted doc":
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions#variable-scope-in-lambda-expressions
Unfortunately this is probably not enough for some people to understand the problem:

Lambdas can refer to outer variables (see Anonymous Methods) that are in scope in the method that defines the lambda function, or in scope in the type that contains the lambda expression. Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected.

You can link to this doc but good examples as demonstrated above are necessary.

@guardrex
Copy link
Collaborator

guardrex commented Feb 2, 2019

Issue has been moved to aspnet/Docs.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants