
在 Blazor 中使用 foreach 循环生成带 @onclick 的元素时,若直接引用循环外变量(如 counter),所有事件处理器会共享同一变量引用,导致点击任意项均传入最终值——这是典型的闭包捕获陷阱。
在 blazor 中使用 foreach 循环生成带 `@onclick` 的元素时,若直接引用循环外变量(如 `counter`),所有事件处理器会共享同一变量引用,导致点击任意项均传入最终值——这是典型的闭包捕获陷阱。
Blazor(尤其是 WebAssembly)中,Razor 模板编译器将 @onclick="() => PlayUrl(counter)" 编译为一个闭包,该闭包按引用捕获外部变量 counter,而非按值捕获其当前迭代状态。因此,当循环结束时,counter 已递增至 3(假设 strings 长度为 2),所有 <span> 的点击事件实际都指向同一个 counter 变量,最终调用 PlayUrl(3)。
✅ 正确做法:在循环体内创建局部副本
只需在 @foreach 内部声明一个局部变量,显式捕获当前迭代的值:
@foreach (string str in strings)
{
var localCounter = counter; // 关键:为本次迭代创建独立副本
<div>
<span @onclick="() => PlayUrl(localCounter)">@localCounter</span>
<span>@str</span>
</div>
@IncrementCounter()
}这样,每次迭代都会生成一个独立的 localCounter 变量,每个 @onclick 闭包捕获的是各自作用域内的值,点击“1”触发 PlayUrl(1),点击“2”触发 PlayUrl(2),完全符合预期。
? 补充说明与最佳实践
-
避免副作用逻辑放在 Razor 表达式中:@InitCounter() 和 @IncrementCounter() 虽能工作,但混用呈现逻辑与状态变更易引发不可预测行为(如重渲染时重复调用)。推荐改用 C# 预处理:
@code { private List<(int Index, string Value)> items = new(); protected override void OnInitialized() { items = strings .Select((s, i) => (Index: i + 1, Value: s)) .ToList(); } }然后在模板中安全遍历:
@foreach (var item in items) { <div> <span @onclick="() => PlayUrl(item.Index)">@item.Index</span> <span>@item.Value</span> </div> } -
更现代写法(推荐):使用 @key 和索引直接遍历
@for (int i = 0; i < strings.Length; i++) { int index = i + 1; // 1-based <div @key="index"> <span @onclick="() => PlayUrl(index)">@index</span> <span>@strings[i]</span> </div> }
⚠️ 注意:切勿在 @onclick 中直接写 () => PlayUrl(counter) 或 () => PlayUrl(i)(若 i 是外部循环变量),务必通过局部变量或结构化数据确保值的确定性。
此问题本质是 C# 闭包行为在 Blazor 渲染上下文中的体现,并非 Blazor Bug,而是开发者需主动规避的语言特性陷阱。掌握变量捕获机制,是编写健壮 Blazor 交互逻辑的关键基础。

















