如何告诉lambda函数在C#中捕获副本而不是引用?

如何告诉lambda函数在C#中捕获副本而不是引用?,c#,loops,lambda,capture,C#,Loops,Lambda,Capture,我一直在学习C#,我正在努力理解lambdas。在下面的示例中,它打印了10次 class Program { delegate void Action(); static void Main(string[] args) { List<Action> actions = new List<Action>(); for (int i = 0; i < 10; ++i ) actions

我一直在学习C#,我正在努力理解lambdas。在下面的示例中,它打印了10次

class Program
{
    delegate void Action();
    static void Main(string[] args)
    {
        List<Action> actions = new List<Action>();

        for (int i = 0; i < 10; ++i )
            actions.Add(()=>Console.WriteLine(i));

        foreach (Action a in actions)
            a();
    }
}
vs


我能找到的唯一解决方案是首先制作本地副本:

for (int i = 0; i < 10; ++i)
{
    int copy = i;
    actions.Add(() => Console.WriteLine(copy));
}
for(int i=0;i<10;++i)
{
int copy=i;
actions.Add(()=>Console.WriteLine(复制));
}

但是我很难理解为什么将副本放在for循环中与使用lambda捕获不同。唯一的解决方案是创建一个本地副本并在lambda中引用它。当在闭包中访问C#(和VB.Net)中的所有变量时,它们将具有引用语义而不是复制/值语义。这两种语言都无法改变这种行为


注意:它实际上并不是作为引用编译的。编译器将变量提升到闭包类中,并将对“i”的访问重定向到给定闭包类内的字段“i”。不过,通常更容易将其视为引用语义

记住lambda表达式实际上只是匿名方法的语法糖

也就是说,您真正想要了解的是匿名方法如何在父范围中使用局部变量


这里有一个链接描述了这一点

编译器所做的是将lambda和lambda捕获的任何变量拉入编译器生成的嵌套类中

编译后,您的示例看起来非常像这样:

class Program
{
        delegate void Action();
        static void Main(string[] args)
        {
                List<Action> actions = new List<Action>();

                DisplayClass1 displayClass1 = new DisplayClass1();
                for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i )
                        actions.Add(new Action(displayClass1.Lambda));

                foreach (Action a in actions)
                        a();
        }

        class DisplayClass1
        {
                int i;
                void Lambda()
                {
                        Console.WriteLine(i);
                }
        }
}
类程序
{
委托无效操作();
静态void Main(字符串[]参数)
{
列表操作=新建列表();
DisplayClass1 DisplayClass1=新的DisplayClass1();
对于(displayClass1.i=0;displayClass1.i<10;++displayClass1.i)
actions.Add(新操作(displayClass1.Lambda));
foreach(动作中的动作a)
a();
}
类别显示类别1
{
int i;
void Lambda()
{
控制台写入线(i);
}
}
}
通过在for循环中创建副本,编译器在每次迭代中生成新对象,如下所示:

for (int i = 0; i < 10; ++i)
{
    DisplayClass1 displayClass1 = new DisplayClass1();
    displayClass1.i = i;
    actions.Add(new Action(displayClass1.Lambda));
}
for(int i=0;i<10;++i)
{
DisplayClass1 DisplayClass1=新的DisplayClass1();
displayClass1.i=i;
actions.Add(新操作(displayClass1.Lambda));
}

因为int的声明在for循环中,所以每次都会重新创建它。有10个不同的整数,都名为“copy”,其中只有一个整数名为“i”,在这个范围内,你可能想读,由我们自己的Jon Skeet撰写。可能的副本我发现很奇怪,这个问题的大多数答案都解释了捕获语义,这对问题的作者来说是非常清楚的,而只有一些提到了解决方案(临时副本)。没有人在回答问题之前读过问题吗?在我看来,描述这种行为的链接是最好的答案。这对于计算表达式试图计算参数时发生的情况非常有帮助。作者无意冒犯,但这不应该是公认的答案。问题是关于强迫C#通过值捕捉变量,这个答案只解释了机制。我仍然不知道如何通过值进行捕获,除非我使用的是
DisplayClass1
而不是lambda(IMO无法实现这一目的)。
class Program
{
        delegate void Action();
        static void Main(string[] args)
        {
                List<Action> actions = new List<Action>();

                DisplayClass1 displayClass1 = new DisplayClass1();
                for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i )
                        actions.Add(new Action(displayClass1.Lambda));

                foreach (Action a in actions)
                        a();
        }

        class DisplayClass1
        {
                int i;
                void Lambda()
                {
                        Console.WriteLine(i);
                }
        }
}
for (int i = 0; i < 10; ++i)
{
    DisplayClass1 displayClass1 = new DisplayClass1();
    displayClass1.i = i;
    actions.Add(new Action(displayClass1.Lambda));
}