Blazor/Razor单击事件与索引参数

13

我的代码如下,但是当我点击<tr>元素时传递的索引参数总是9。

这是因为在传递给组件作为数据的表中有9行。看起来索引始终是变量“i”的值,而这个值是在最后设置的...... 在这种情况下,在foreach循环中的最后一行之后i的值为9,所以我在点击表中所有行时都会得到索引参数9...

我的代码有什么问题没有为每一行点击设置 值?

    <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);
    }
}

在将计数器 i 传递给 lambda 函数之前,您需要对其进行缓冲。 - Second Person Shooter
2个回答

19

概况

实际上,您的问题是关于 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

Blazor

引用自官方文档关于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循环示例中的 i。否则,所有lambda表达式都使用相同的变量,这会导致在所有lambda表达式中使用相同的值。请始终将该变量的值捕获到一个本地变量中,然后再使用它。在上面的示例中,循环变量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>
}

1
非常感谢!它起作用了...为什么我需要每次定义一个int然后赋值?请问是什么原因呢? - Codegeek
谢谢,它确实有效...明白了!没想到在传递给lambda时需要这样做。 - Codegeek
@Codegeek:有关详情,请阅读这个答案(以及给出的链接) - Second Person Shooter

4
这是因为 i 的值没有被呈现到页面上(就像在MVC/Razor Pages中),只有在触发事件时才会进行评估。在页面渲染之前,您不会触发事件,因此在此时循环已经完成,所以 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文档存储库这里有一个很好的讨论,其中解释了问题如下:

问题通常出现在事件处理程序和绑定表达式中。 我们应该解释一下,在 for 循环中,我们只有一个迭代变量, 而在 foreach 中,我们为每个迭代创建一个新变量。我们应该 解释一下,当执行 for / foreach 循环时,HTML内容会被渲染, 但是事件处理程序稍后才会调用。以下是演示一个错误和两个正确解决方案的示例代码。

如果您从MVC / Razor Page背景过来,使用 for 是正常行为,这一切尤其令人困惑。不同之处在于,在MVC中,i 的值实际上写入到页面中的html中,因此在示例中的每一行表格中都不同。

根据上面链接的问题和@Fat Man No Neck的答案,造成这种问题的根本原因是由于lambda表达式在 forforeach 循环中的行为差异。这不是Blazor的错误,这是C#的工作方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接