我有以下简单的代码:
static void Main(string[] args)
{
int j = 0;
Func<int> 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,因为匿名方法在生成的类中由编译器具有其自己的变量。那么它为什么还要修改外部变量?
答案 0 :(得分:10)
外部变量和闭包中的变量是相同的变量。您的计划相当于:
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<int> f = closure.Method;
int myStr = f();
Console.WriteLine(myStr);
Console.WriteLine(closure.j);
Console.Read();
}
现在很明显为什么你会得到观察到的结果?
答案 1 :(得分:4)
这是闭包的工作方式,它们捕获变量,而不是值。因此j
将被更改。
如果你不想要,你可以这样做:
static void Main(string[] args)
{
int j = 0;
Func<int> 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<Foo> f = () =>
{
Foo k = new Foo(j.N); // Can't just say k = j;
for (int i = 0; i < 3; i++)
{
k.N += 1;
}
return k;
};
Console.WriteLine(f().N);
Console.WriteLine(j.N);
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
相同的字符串的引用。
答案 2 :(得分:3)
语言规范说:
匿名方法类似于Lisp编程语言中的lambda函数。 C#2.0支持创建“闭包”,其中匿名方法访问周围的局部变量和参数。
你的案例中的'j'是变量。