为啥我的委托只使用我的 foreach 循环中的最后一项?

Posted

技术标签:

【中文标题】为啥我的委托只使用我的 foreach 循环中的最后一项?【英文标题】: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; 
    

【问题讨论】:

研究“闭环” 检查这个:***.com/questions/271440/… 【参考方案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 将此强调为“访问修改后的闭包”警告,并建议引入临时/迭代范围变量。

以上是关于为啥我的委托只使用我的 foreach 循环中的最后一项?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 foreach 循环与 users.json 文件中的键不匹配

为啥我的 forEach 循环没有编辑我的数组? [复制]

当我使用 for 循环时,为啥我的代码没有在 forEach 中等待? [复制]

为啥我的 foreach 比我的 for 循环快? [复制]

为啥 for-each 循环适用于数组? (爪哇)

Java中foreach为啥不能给数组赋值