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

外部変数とクロージャ内の変数は同じ variable です。あなたのプログラムは以下と同等です:

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、オブジェクトへの参照のコピーを格納します。参照されているオブジェクトのコピーが 1 つ残っているため、そのオブジェクトを変更すると両方の変数に影響します。

元の変数を更新せずに、参照型にクロージャーを使用する方法の例を次に示します。

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不変性を考える 1 つの方法は、文字列の値を更新するたびに、実際には新しいインスタンスを作成していることです。と言うk = k + "1"ようなものk = new String(k + "1")です。その時点で、 と同じ文字列への参照ではなくなりますj

于 2013-03-07T15:54:04.850 に答える
4

言語仕様には次のように書かれています。

無名メソッドは、Lisp プログラミング言語のラムダ関数に似ています。C# 2.0 は、匿名メソッドが周囲のローカル変数とパラメーターにアクセスする「クロージャー」の作成をサポートしています。

そして、あなたの場合の「j」は周囲の変数です。

于 2013-03-07T15:54:30.087 に答える