変更されたクロージャーへのアクセス


316
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はこれが「変更されたクロージャーへのアクセス」であると不平を言っていますが、上記はうまく機能しているようです。誰でもこれに光を当てることができますか?

(このトピックはここに続きました


6
リンクが出ているが、私はWebArchiveでそれを見つけた:web.archive.org/web/20150326104221/http://www.jarloo.com/...
エリック・ウー

回答:


314

この場合、実際はループ内でデリゲートを実行しているため、問題はありません。

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

要するに、それは潜在的な罠として認識されるべきものですが、この場合それはあなたを傷つけません。

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


29

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

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

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


4

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

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

MSDNの外部変数

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.