让我的所有时间段变成 24:00 的 Blazor 闭包 Bug 🕛 🕛

发布: (2026年1月4日 GMT+8 03:41)
4 min read
原文: Dev.to

Source: Dev.to

神秘的 Bug

我在 Blazor 中构建会议室预订系统时遇到了一个奇怪的 bug。我的时间轴组件显示的时间段是从上午 6 点到晚上 11 点,但无论点击哪个时间段,控制台总是显示 24:00

// Console output – ALWAYS showed 24!
Time slot selected: 24:0
Start: 06-01-2026 00:00:00, End: 06-01-2026 01:00:00

视觉上时间轴是正确的,但每一个按钮都失效了。为什么所有从上午 6 点到晚上 11 点的按钮都会发送 “24” 作为小时?

最初的假设

  • 时区问题?– 检查了 DateTime.Kind、UTC 转换
  • DateTime 解析 bug?– 添加了大量验证
  • 浏览器 datetime-local bug?– 在多个浏览器上测试

调试了 3 小时后,我添加了更多日志:

private async Task OnTimeSlotClick(int hour, int minute)
{
    Console.WriteLine($"[DEBUG] Clicked hour: {hour}");
    // Always showed 24, regardless of which button!
}

生成按钮的循环 看起来 完全正确:

@for (int hour = 6; hour < 24; hour++)
{
    <button @onclick="() => OnTimeSlotClick(hour, 0)">
        @hour:00
    </button>
}

按钮显示 6:00, 7:00, 8:00…,但全部在点击时调用 OnTimeSlotClick(24, 0)

闭包捕获的陷阱

在 C# 中,lambda 捕获的是变量,而不是它们的值。当任何按钮被点击时,循环已经结束,hour == 24(循环在 hour 达到 24 时退出)。

// When loop finishes: hour = 24
// ALL click handlers now reference hour = 24!

解决方案

1. 使用 foreachEnumerable.Range

@foreach (var hour in Enumerable.Range(6, 18))
{
    <button @onclick="() => OnTimeSlotClick(hour, 0)">
        @hour:00
    </button>
}

foreach 的每一次迭代都会创建一个新变量,避免共享捕获。

2. 在 for 循环内部做局部副本(最常见的修复)

@for (int hour = 6; hour < 24; hour++)
{
    var currentHour = hour;
    <button @onclick="() => OnTimeSlotClick(currentHour, 0)">
        @currentHour:00
    </button>
}

3. 使用带参数的方法

@for (int hour = 6; hour < 24; hour++)
{
    <button @onclick="() => HandleHourClick(hour)">
        @hour:00
    </button>
}

@code {
    private void HandleHourClick(int hour) // ✅ New parameter each call
    {
        OnTimeSlotClick(hour, 0);
    }
}

4. 使用 @key 强制重新渲染

@for (int hour = 6; hour < 24; hour++)
{
    <button @key="hour" @onclick="() => OnTimeSlotClick(hour, 0)">
        @hour:00
    </button>
}

为什么 Blazor 更容易出现此类 bug

  • 事件处理器是 稍后 执行的 lambda。
  • 组件生命周期会把执行延迟到渲染之后。
  • 状态变化会触发重新渲染,可能会改变已捕获的变量。
  • 异步特性导致 lambda 常常在循环结束很久之后才运行。

这不仅是 Blazor 的问题

  • JavaScriptvar 在循环中的同样问题。
  • C# – 任意在循环内部的 lambda。
  • Java – 匿名内部类捕获循环变量。
  • Python – 默认参数在定义时求值。

经验法则

在循环内部创建 lambda 或事件处理器时,捕获值而不是变量

  • 在 lambda 内部立即记录日志。
  • 使用硬编码值进行测试以隔离问题。
  • 在 lambda 之前创建局部副本(var current = loopVar)。
  • 将带 lambda 的循环视为“始终可疑”。

结束语

这个 bug 让我明白,最看似简单的代码也可能产生最令人惊讶的行为。如果不小心,循环中的 int 会变成所有事件处理器共享的引用。

讨论

  • 你是否也被闭包捕获 bug 咬过?
  • 你在 Blazor 中遇到过哪些其他坑?
  • 分享你最喜欢的调试故事吧!
Back to Blog

相关文章

阅读更多 »

在 C# 中将 RTF 转换为 PDF

概述 RTF(Rich Text Format)是一种跨平台的格式,广泛用于文档编辑和数据交换。相比之下,PDF 则是文档分发的理想选择……