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

mea*_*nny 4 c# delegates timer

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

确实如此简单,实际上确实创造了我的计时器.但是,我希望每个已发生的事件都使用它传入的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;
}
Run Code Online (Sandbox Code Playgroud)

然后我把它object sender放回原来的物体,然后把它Tag从它上面剥下来,这给了我每个独特计时器的正确时间表.

再说一遍,为什么在所有Timers的foreach循环delegate { }中使用最后一个传递ScheduleElement

编辑1

Timer类

public TimerEx : Timer {

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

    private Object _Tag;

    public Object Tag {
        get { return _Tag; }
        set { _Tag = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 9

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

有关详细信息,请参阅Eric Lippert的文章Closing over loop variable被认为是有害的.

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

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); };
Run Code Online (Sandbox Code Playgroud)

请注意,C#5更改了此foreach循环的行为.如果使用最新的编译器进行编译,则问题不再存在.