使用for循环和带闭包的foreach循环的不同行为

时间:2010-12-02 23:21:17

标签: c# lambda

我无法解释我遇到的问题。基本上,如果我在foreach循环中使用lambda语法,那么我会得到一个不同的答案,而不是在for循环中使用它。在下面的代码中,我在“调度程序”类中注册一个委托。然后我在另一个委托中将委托包装出来并返回这些包装委托的列表。然后我执行它们。执行包装函数列表的预期输出为1,2。但是当我组合lambda和foreach循环时,我没有看到。

这不是引起问题的代码,而是我可以用来重现它的最简单的情况。我不想讨论这个用例,我更好奇为什么我得到的行为我没想到。如果我使用lambda语法使用下面的foreach循环,它将失败。如果我使用新的Action()语法和foreach它可以工作,如果我在for循环中使用lambda语法它可以工作。任何人都可以解释这里发生了什么。这让我很难过。

    public class Holder
{
    public Holder(int ID, Dispatcher disp)
    {
        this.ID = ID;
        disp.Register(Something);
    }
    public int ID { get; set; }
    private void Something(int test) { Console.WriteLine(ID.ToString()); }
}

public class Dispatcher
{
    List<Action<int>> m_Holder = new List<Action<int>>();

    public void Register(Action<int> func)
    {
        m_Holder.Add(func);
    }

    public List<Action<int>> ReturnWrappedList()
    {
        List<Action<int>> temp = new List<Action<int>>();

        //for (int i = 0; i < m_Holder.Count; i++)      //Works - gives 1, 2
        //{
        //    var action = m_Holder[i];
        //    temp.Add(p => action(p));
        //}

        foreach (var action in m_Holder)
        {
            temp.Add(p => action(p)); //Fails - gives 2,2
            //temp.Add(new Action<int>(action)); Works - gives 1,2
        }

        return temp;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var disp = new Dispatcher();
        var hold1 = new Holder(1, disp);
        var hold2 = new Holder(2, disp);
        disp.ReturnWrappedList().ForEach(p => p(1));
    }
}

3 个答案:

答案 0 :(得分:4)

这是臭名昭着的“关闭循环变量”问题。

答案 1 :(得分:0)

你试过了吗?

foreach (var action in m_Holder)
{
    var a = action;
    temp.Add(p => a(p));
}

答案 2 :(得分:0)

这是捕获闭包的经典问题,其范围与您的预期不符。在foreach中,action具有外部作用域,因此执行会捕获循环的 last 值。在for case中,您在内部作用域中创建动作,因此闭包在每次迭代时都超过了本地值。