场景:我正在构建一个计划系统和每个计时器事件,我想运行一个自定义方法,而不是通常的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; }
}
}
答案 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
循环的此行为。如果使用最新的编译器进行编译,则问题不再存在。