I am creating a Blazor webassembly application and ran into an issue when looping over content to generate urls to mp3 files.
The media files are called "1.mp3", "2.mp3", "3.mp3" … etc.
Assume you have a razor page with this content:
@* set counter variable to 1 *@
@InitCounter()
@foreach(string str in strings)
{
<div>
<span @onclick="() => PlayUrl(counter)">@counter</span>
<span>@str</span>
</div>
@* Increment counter variable by 1 *@
@IncrementCounter()
}
@code
{
public int counter = 1;
public string InitCounter()
{
counter = 1;
return string.Empty;
}
public string IncrementCounter()
{
counter++;
return string.Empty;
}
}
I have troubles understanding why the function PlayUrl
is being called with the wrong value, when I click on that particular span element in the browser?
Lets says that strings
is defined this way:
strings[] strings = { "aaa", "bbb" };
This should generate some html similar to this:
<div>
<span b-2e01zi41cp="">1</span>
<span b-2e01zi41cp="">aaa</span>
</div>
<div>
<span b-2e01zi41cp="">2</span>
<span b-2e01zi41cp="">bbb</span>
</div>
No matter which span element that I click on, it will always call PlayUrl with the argument ‘3’.
Is there anyone that can explain why and how to make Blazor pass the right value to PlayUrl?
2
Answers
You have turned a
foreach()
into afor()
loop, and for-loops and lambda functions are a dangerous combination.for an explanation, look up one of the countless duplicates, search term "closing over the loop variable"
In Blazor, when you use a lambda expression
(() => PlayUrl(counter))
inside a loop, it captures the variablecounter
by reference, not by value. This means that everylambda expression
in your loop is referring to thesame counter variable
, which is whyPlayUrl
always receives the final value of counter after the loop has completed.