web-dev-qa-db-ja.com

変更された閉鎖へのアクセス

string [] files = new string[2];
files[0] = "ThinkFarAhead.Example.Settings.Configuration_Local.xml";
files[1] = "ThinkFarAhead.Example.Settings.Configuration_Global.xml";

//Resharper complains this is an "access to modified closure"
for (int i = 0; i < files.Length; i++ )
{
    // Resharper disable AccessToModifiedClosure
    if(Array.Exists(Assembly.GetExecutingAssembly().GetManifestResourceNames(),
    delegate(string name) { return name.Equals(files[i]); }))
         return Assembly.GetExecutingAssembly().GetManifestResourceStream(files[i]);
    // ReSharper restore AccessToModifiedClosure
}

ReSharperはこれが「修正されたクロージャーへのアクセス」であると文句を言いますが、上記はうまくいくようです。誰もこれに光を当てることができますか?

(このトピックは続きます here

305
Vyas Bharghava

この場合、実際にはデリゲートwithinループを実行しているので大丈夫です。

ただし、デリゲートを保存して後で使用する場合、ファイルにアクセスしようとするとすべてのデリゲートが例外をスローすることがわかります[i]-変数iデリゲート作成時の値ではなく。

要するに、これはpotentialトラップとして認識すべきものですが、この場合はあなたを傷つけません。

結果が直観に反するより複雑な例については、 このページの下部 を参照してください。

306
Jon Skeet

これは古い質問であることは知っていますが、最近クロージャーを研究しており、コードサンプルが役立つかもしれないと考えました。舞台裏では、コンパイラーは、関数呼び出しの字句クロージャーを表すクラスを生成しています。おそらく次のようになります。

private sealed class Closure
{
    public string[] files;
    public int i;

    public bool YourAnonymousMethod(string name)
    {
        return name.Equals(this.files[this.i]);
    }
}

前述のように、述部は作成直後に呼び出されるため、関数は機能します。コンパイラは次のようなものを生成します:

private string Works()
{
    var closure = new Closure();

    closure.files = new string[3];
    closure.files[0] = "notfoo";
    closure.files[1] = "bar";
    closure.files[2] = "notbaz";

    var arrayToSearch = new string[] { "foo", "bar", "baz" };

    //this works, because the predicates are being executed during the loop
    for (closure.i = 0; closure.i < closure.files.Length; closure.i++)
    {
        if (Array.Exists(arrayToSearch, closure.YourAnonymousMethod))
            return closure.files[closure.i];
    }

    return null;
}

一方、述語を格納して後で呼び出す場合、述語へのすべての呼び出しは、クロージャークラスの同じインスタンスで同じメソッドを呼び出しているため、同じ値を使用することがわかります。私。

28
gerrard00

「ファイル」は、キャプチャされた外部変数です。匿名デリゲート関数によってキャプチャされたためです。その有効期間は、匿名デリゲート関数によって延長されます。

キャプチャされた外部変数外部変数が匿名関数によって参照される場合、外部変数は匿名関数によってキャプチャされたと言われます。通常、ローカル変数の有効期間は、関連付けられているブロックまたはステートメントの実行に制限されます(ローカル変数)。ただし、キャプチャされた外部変数のライフタイムは、少なくとも匿名関数から作成されたデリゲートまたは式ツリーがガベージコレクションの対象になるまで延長されます。

MSDNの外部変数

ローカル変数または値パラメーターが匿名関数によってキャプチャされると、ローカル変数またはパラメーターは固定変数(固定変数および可動変数)とは見なされなくなり、代わりに可動変数と見なされます。したがって、キャプチャされた外部変数のアドレスを取得する安全でないコードは、最初にfixedステートメントを使用して変数を修正する必要があります。キャプチャされていない変数とは異なり、キャプチャされたローカル変数は複数の実行スレッドに同時に公開されることに注意してください。

2
chris hu