【问题标题】:Why does my delegate only use the last item from my foreach loop?为什么我的委托只使用我的 foreach 循环中的最后一项?
【发布时间】:2013-07-15 08:33:57
【问题描述】:

场景:我正在构建一个调度系统,我希望每个计时器事件都运行一个自定义方法,而不是通常的Timer.Elapsed 事件。

所以我写了这样的东西。

foreach (ScheduleElement schedule in schedules) {
    TimeSpan timeToRun = CalculateTime(schedule);
    schedule.Timer = new Timer(timeToRun.TotalMilliseconds);
    schedule.Timer.Elapsed += delegate { Refresh_Timer(schedule); };
    schedule.Timer.AutoReset = true;
    schedule.Timer.Enabled = true;
}

好的,很简单,实际上确实创建了我的计时器。但是,我希望每个 elapsed 事件都使用它传入的 schedule 元素运行。我的问题是,为什么 Elapsed 事件仅在 for 循环中的最后一个 ScheduleElement 中为每个 Timer.Elapsed 事件传递。

现在我知道是什么解决了它,我只是不知道为什么。如果我回滚到原始 Timer.Elapsed 事件并用我自己的类扩展 Timer 类,我可以解决它。像这样。

解决方法:

foreach (ScheduleElement schedule in schedules) {
    TimeSpan timeToRun = CalculateTime(schedule);
    schedule.Timer = new TimerEx(timeToRun.TotalMilliseconds);
    schedule.Timer.Elapsed +=new System.Timers.ElapsedEventHandler(Refresh_Timer);
    schedule.Timer.Tag = schedule;
    schedule.Timer.AutoReset = true;
    schedule.Timer.Enabled = true;
}

然后我将object sender 转换回其原始对象,并从其中窃取Tag 属性,这为我提供了每个唯一计时器的正确时间表。

那么,为什么在所有计时器的 foreach 循环中只使用delegate { } 只传递最后一个ScheduleElement

编辑 1

定时器类

public TimerEx : Timer {

    public TimerEx(double interval) : base(interval) { }

    private Object _Tag;

    public Object Tag {
        get { return _Tag; }
        set { _Tag = value; }
    }
}

【问题讨论】:

标签: c# timer delegates


【解决方案1】:

这是因为您在委托中使用了闭包,并且它关闭了同一个变量,该变量为 foreach 循环的每次迭代共享。

详情见Eric Lippert的文章Closing over the loop variable considered harmful

在这种情况下,您可以轻松地使用临时修复它:

foreach (ScheduleElement schedule in schedules) {
    TimeSpan timeToRun = CalculateTime(schedule);
    schedule.Timer = new Timer(timeToRun.TotalMilliseconds);

    // Make a temporary variable in the proper scope, and close over it instead
    var temp = schedule;
    schedule.Timer.Elapsed += delegate { Refresh_Timer(temp); };

请注意,C# 5 更改了 foreach 循环的这种行为。如果你用最新的编译器编译它,问题就不再存在了。

【讨论】:

  • 在哪里可以找到有关使用最新编译器的更多信息?我不太确定我的 VS 使用的是什么,我有 2010 年。
  • @meanbunny 您需要使用 VS 2012 或更高版本才能获得新行为。它在 VS 2012 发布的 C# 5 中进行了更改。
  • 好的,谢谢,很好的回答,我真的很感激!距离我接受还有 4 分钟。
  • 也许值得注意的是,ReSharper 将此强调为“访问修改后的闭包”警告,并建议引入临时/迭代范围变量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-16
  • 2022-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多