Closure捕获变量也会修改原始变量

我有以下简单的代码:

static void Main(string[] args) { int j = 0; Func f = () => { for (int i = 0; i < 3; i++) { j += i; } return j; }; int myStr = f(); Console.WriteLine(myStr); Console.WriteLine(j); Console.Read(); } 

根据我在涉及闭包时所读到的内容,编译器会创建一个新类型,以便它可以存储捕获的变量并维护对它的引用。 但是,当我运行以下代码时,两个打印行显示3.我期待0和3,因为匿名方法在生成的类中由编译器具有自己的变量。 那么它为什么还要修改外部变量呢?

外部变量和闭包中的变量相同的变量 。 您的计划相当于:

 private class Closure { public int j; public int Method() { for (int i = 0; i < 3; i++) { this.j += i; } return this.j; } } static void Main(string[] args) { Closure closure = new Closure(); closure.j = 0; Func f = closure.Method; int myStr = f(); Console.WriteLine(myStr); Console.WriteLine(closure.j); Console.Read(); } 

现在很明显为什么你得到观察到的结果?

这就是闭包的工作方式,它们捕获变量,而不是值。 所以j将被改变。

如果你不想那样,你可以这样做:

 static void Main(string[] args) { int j = 0; Func f = () => { int k = j; for (int i = 0; i < 3; i++) { k += i; } return k; }; int myStr = f(); Console.WriteLine(myStr); Console.WriteLine(j); Console.Read(); } 

j仍被闭包捕获,但未被修改。 仅修改副本k

编辑:

您正确地注意到这对参考类型不起作用。 在那种情况下, k = j存储对象的引用的副本。 仍然有一个引用对象的副本,因此对该对象的任何修改都将影响这两个变量。

下面是一个如何使用闭包作为引用类型而不更新原始变量的示例:

 static void Main(string[] args) { Foo j = new Foo(0); Func f = () => { Foo k = new Foo(jN); // Can't just say k = j; for (int i = 0; i < 3; i++) { kN += 1; } return k; }; Console.WriteLine(f().N); Console.WriteLine(jN); Console.Read(); } public class Foo { public int N { get; set; } public Foo(int n) { N = n; } } 

但是,字符串是不可变的引用类型,实际上你可以k = j ,与任意引用类型不同。 考虑不变性的一种方法是每次更新字符串的值时,实际上都是在创建一个新实例。 所以k = k + "1"就像是说k = new String(k + "1") 。 此时,它不再是与j相同的字符串的引用。

语言规范说:

匿名方法类似于Lisp编程语言中的lambda函数。 C#2.0支持创建“闭包”,其中匿名方法访问周围的局部变量和参数。

在你的情况下’j’是围绕变量。