我有以下代码,但当我单击
<tr>
元素时传递的索引参数始终为 9。
那是因为表中有 9 行作为数据传递给组件。 所以看起来索引总是最后设置的变量“i”的值...在这种情况下,foreach循环中最后一行之后的i的值是9,所以我在单击所有按钮时得到索引参数为9表中的行...
我的代码中有什么问题没有为每行 onclick 设置
i
值。
<table border="1">
@for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
<tr @onclick="(() => RowSelect(i))">
@foreach (ModelColumn col in ListData.ListColumns)
{
<td>@ListData.DataView.Table.Rows[i][col.Name]</td>
}
</tr>
}
</table>
@code {
private async Task RowSelect(int rowIndex)
{
await ListRowSelected.InvokeAsync(rowIndex);
}
}
实际上你的问题是关于捕获局部变量的 lambda 。为了简单起见,请参阅以下使用控制台应用程序的模拟。
class Program
{
static void Main(string[] args)
{
Action[] acts = new Action[3];
for (int i = 0; i < 3; i++)
acts[i] = (() => Job(i));
foreach (var act in acts) act?.Invoke();
}
static void Job(int i) => Console.WriteLine(i);
}
它将输出三次
3, 3, 3
而不是0, 1, 2
。
引用官方文档关于EventCallback:
<h2>@message</h2>
@for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<button class="btn btn-primary"
@onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
}
@code {
private string message = "Select a button to learn its position.";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
message = $"You selected Button #{buttonNumber} at " +
$"mouse position: {e.ClientX} X {e.ClientY}.";
}
}
请勿直接在 lambda 表达式中使用循环变量,例如前面的 for 循环示例中的
。否则,所有 lambda 表达式都会使用相同的变量,这会导致所有 lambda 中使用相同的值。始终捕获局部变量中的变量值,然后使用它。在前面的示例中,循环变量 i 被分配给i
。buttonNumber
因此您需要为
i
制作一份本地副本,如下所示。
@for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
int buffer=i;
<tr @onclick="(() => RowSelect(buffer))">
@foreach (ModelColumn col in ListData.ListColumns)
{
<td>@ListData.DataView.Table.Rows[i][col.Name]</td>
}
</tr>
}
发生这种情况是因为
i
的值未呈现到页面(就像在 MVC/Razor 页面中一样),它只是在触发事件时进行评估。 在页面呈现之前,您不会触发该事件,因此到那时循环将完成,因此 i
的值将始终是循环结束时的值。
有几种方法可以处理这个问题,如果合适的话可以使用
foreach
循环(这是大多数 Blazor 文档示例所做的),或者在循环内声明一个局部变量:
@for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
int local_i=i;
// Now use local_i instead of i in the code inside the for loop
}
这里的 Blazor Docs 存储库对此进行了很好的讨论,它解释了该问题如下:
问题通常出现在事件处理程序和绑定表达式中。 我们应该解释一下,在
循环中我们只有一个迭代变量 在for
中,我们为每次迭代都有一个新变量。我们应该 解释当foreach
/for
循环出现时,会渲染 HTML 内容 执行,但稍后调用事件处理程序。这是一个示例代码 展示一个错误的解决方案和两个好的解决方案。foreach
如果您从 MVC/Razor 页面背景进入 Blazor,那么这一切尤其令人困惑,其中使用
for
是正常行为。 不同之处在于,在 MVC 中,i
的值实际上写入页面上的 html,因此示例中表格的每一行都会有所不同。
根据上面链接的问题和 @Fat Man No Neck 的回答,其根本原因在于
for
和 foreach
循环与 lambda 表达式的行为差异。 这不是 Blazor bug,这只是 C# 的工作原理。
我刚刚通过下面的示例奋力拼搏,我想分享一下。 这个示例使用
foreach
而不是 for
,但我仍然被“触发事件时评估索引值”问题所困扰。 下面的代码按照我想要的方式工作,但我将解释如何将其更改为我最初拥有的方式 - 即代码模式不按预期工作:
@{ int itemIndex = -1; }
<ul>
@foreach( var mItem in Anchors ) {
itemIndex++;
var idx = itemIndex;
<li @onpointerup="() => RunMyRoutine( Anchors[idx].Label )">
<a href="@Anchors[idx].URL">@Anchors[idx].Label</a>
</li>
}
</ul>
上面的三个地方都有 [idx] ——如果你将它们更改为在 foreach 执行时更改的值,
itemIndex
,你就会遇到问题。 问题是,虽然 <a>
标签是按预期构建的,且 URL 正确且标签正确,但当 onpointerup
事件发生时,发送到例程的值是 always 最后一个值,即最大索引价值。 这就是事件发生时的问题,索引值在事件时评估,并且它是代码循环迭代期间设置的最后一个值。
这个问题可以通过
var idx = itemIndex
行解决。 随着 foreach 的迭代, idx 不断获得正确的值,令人惊奇的是,传递给它的 idx 获得了正确的预期值,该值与代码的标记创建部分期间评估的值相匹配(锚点,在这种情况下)。我希望这个答案有帮助,我当然欢迎任何人编辑这个答案以提高清晰度。