为什么我的代理人只使用我的foreach循环中的最后一项?

时间:2013-07-16 23:42:18

标签: c# timer delegates

场景:我正在构建一个计划系统和每个计时器事件,我想运行一个自定义方法,而不是通常的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;
}

好的非常简单,确实创造了我的计时器。但是,我希望每个已发生的事件都使用它传入的schedule元素运行。我的问题是,为什么Elapsed事件只传递每个Timer.Elapsed事件的for循环中的最后一个ScheduleElement。

现在我知道修复了什么,我不知道为什么。如果我回滚到原始的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属性从其中删除,这为我提供了每个唯一计时器的正确计划。

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

编辑1

Timer类

public TimerEx : Timer {

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

    private Object _Tag;

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

1 个答案:

答案 0 :(得分:9)

这是因为你在委托中使用了闭包,它关闭了同一个变量,该变量在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循环的此行为。如果使用最新的编译器进行编译,则问题不再存在。