2

この不自然な例を考えてみましょう:

public static class Test {

    private static List<Action> actions = new List<Action>();
    private static Int32 _foo = 123;

    public static void Foo() {

        Int32       foo = _foo += 123;
        Object      bar = new Object();
        IDisposable baz = GetExpensiveObject();

        Action callback = new Action(delegate() {

            DoSomething( foo, bar, baz );

            baz.Dispose();
        });

        foo = 456;
        bar = new Object();

        actions.Add( callback );
    }

    public static void Main() {

        Foo();
        Foo();

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

を見てMainFooが 2 回呼び出され、その後actions(今では 2 つのActionインスタンス) の内容が実行されたfoobarbazますcallback

callbackが呼び出されない場合は、破棄されbaz ます(参照が?にcallback含まれているため)。呼び出されたものは破棄されますか?actionsactions.Clear()baz

(私は、テストのためにコンパイラーまたは IDE がオンになっているコンピューターを使用していません)

4

2 に答える 2

1

ローカル変数の有効期間は、匿名メソッドがそこで使用されている場合、その有効期間に合わせて延長されることに注意してください。これは、匿名メソッドを作成する時点で変数の値がコピーされるという意味ではありません。したがって、「DoSomething」は「456」と 2 番目に作成されたオブジェクトで毎回呼び出されます。

新しい WinForms-Project を作成し、フォームに新しいボタンを配置して、次のコードを追加すると、確認できます。

private void Form1_Load(object sender, EventArgs e)
    {
        int i = 123;

        this.button1.Click += (Lsender, Le) => { MessageBox.Show(i.ToString()); };

        i = 456;
    }

ここでは参照型に注意してください。

{
private static Foo(object value)
{
    object bar = value;
    //...
}

private static void Main()
{
    object obj = new object();

    Foo(obj);
    Foo(obj);

    //...
}

}

その場合、各コールバックには独自の変数「bar」がありますが、それぞれがヒープ メモリ内の同じオブジェクトを参照しています。

于 2013-04-03T09:29:44.630 に答える
1

匿名メソッドはコンパイラによって書き換えられ、ローカル スコープと同じヒープ上のメモリ領域への参照が保持されます。ガベージ コレクターは、この参照がアクティブであることを検出し、匿名メソッドも収集されるまでターゲットをガベージ コレクションしません。

ただし...ヒープではなく、新しいメソッド呼び出しによって上書きされる可能性のあるスタックに割り当てている場合はどうなりますか? ;)

private static void Main(String[] args) {
    var rng = CreateRNG();
    Console.WriteLine(rng());
    Console.WriteLine(rng());
    Console.ReadLine();
}

private static unsafe Func<Int32> CreateRNG() {
    var v = stackalloc Int32[1];
    v[0] = 4;
    return () => v[0];
}

このコードは、最初の呼び出しで 4 を出力し、2 回目の呼び出しで半乱数を出力します。

Reflector を使用して抽出され、手動でクリーンアップされ、コンパイルできるようにメソッドの名前が変更された実際のコード (コンパイラは、自動生成されたメソッド名で <> などの特殊文字を使用します):

private static unsafe Func<Int32> CreateRNG() {
    Int32* numPtr = stackalloc Int32[1];

    var class2 = new __c__DisplayClass1();
    class2.v = numPtr;
    class2.v[0] = 4;
    return new Func<Int32>(class2._CreateRNG_b__0);
}

[CompilerGenerated]
public sealed class __c__DisplayClass1 {
    public unsafe Int32* v;

    public unsafe Int32 _CreateRNG_b__0() {
        return v[0];
    }
}

これは、コンパイラが匿名メソッドを新しい関数に書き換えていることを示しています。この場合は、参照されたローカル値を保持する新しいクラスです。ローカル参照を保持する必要がない場合、クラスは必要ありません。

Func<Int32>また、返された を呼び出すと、値が簡単に読み取られるため、最初の呼び出しが機能することも推測できます。メソッド本体は非常に小さいため、おそらくインライン化できます。値 4 が に渡されConsole.WriteLine、そのメソッド呼び出しがおそらくスタックを上書きし (またはメソッド呼び出しがスタックを上書きConsole.WriteLineし)、ポインタが指す値を変更します。

于 2013-04-03T09:51:49.537 に答える