6

我有以下简单的代码:

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,因为匿名方法在编译器生成的类中有自己的变量。那么为什么它还要修改外部变量呢?

4

3 回答 3

13

外部变量和闭包中的变量是同一个变量。您的程序相当于:

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();
}

现在很清楚为什么你会得到观察到的结果吗?

于 2013-03-07T16:00:40.350 回答
5

这就是闭包的工作方式,它们捕获变量,而不是值。所以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.

于 2013-03-07T15:54:04.850 回答
4

语言规范说:

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

在你的情况下,'j' 是周围的变量。

于 2013-03-07T15:54:30.087 回答