Akses ke Penutupan yang Dimodifikasi

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
}

Di atas tampaknya berfungsi dengan baik meskipun ReSharper mengeluh bahwa ini adalah "akses ke penutupan dimodifikasi". Adakah yang bisa menjelaskan ini?

(topik ini berlanjut di sini )

Vyas Bharghava
sumber
6
Tautan keluar, tetapi saya menemukannya di WebArchive: web.archive.org/web/20150326104221/http://www.jarloo.com/…
Eric Wu

Jawaban:

314

Dalam hal ini, tidak apa-apa, karena Anda benar-benar mengeksekusi delegasi dalam loop.

Namun, jika Anda menyimpan delegasi dan menggunakannya nanti, Anda akan menemukan bahwa semua delegasi akan memberikan pengecualian ketika mencoba mengakses file [i] - mereka menangkap variabel i daripada nilainya pada saat delegasi penciptaan.

Singkatnya, ini adalah sesuatu yang harus diperhatikan sebagai jebakan potensial , tetapi dalam hal ini tidak akan menyakiti Anda.

Lihat bagian bawah halaman ini untuk contoh yang lebih kompleks di mana hasilnya berlawanan dengan intuisi.

Jon Skeet
sumber
29

Saya tahu ini adalah pertanyaan lama, tetapi saya baru-baru ini mempelajari penutupan dan berpikir contoh kode mungkin berguna. Di belakang layar, kompiler menghasilkan kelas yang mewakili penutupan leksikal untuk panggilan fungsi Anda. Mungkin terlihat seperti:

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

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

Seperti disebutkan di atas, fungsi Anda berfungsi karena predikat dipanggil segera setelah pembuatan. Kompiler akan menghasilkan sesuatu seperti:

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

Di sisi lain, jika Anda menyimpan dan kemudian memanggil predikat, Anda akan melihat bahwa setiap panggilan ke predikat akan benar-benar memanggil metode yang sama pada instance yang sama dari kelas penutupan dan karenanya akan menggunakan nilai yang sama untuk saya.

gerrard00
sumber
4

"file" adalah variabel luar yang ditangkap karena telah ditangkap oleh fungsi delegasi anonim. Masa pakainya diperpanjang oleh fungsi delegasi anonim.

Variabel luar yang diambil Ketika variabel luar direferensikan oleh fungsi anonim, variabel luar dikatakan telah ditangkap oleh fungsi anonim. Biasanya, masa hidup variabel lokal terbatas pada pelaksanaan blok atau pernyataan yang terkait (variabel lokal). Namun, masa pakai variabel luar yang ditangkap diperpanjang setidaknya hingga delegasi atau pohon ekspresi yang dibuat dari fungsi anonim menjadi memenuhi syarat untuk pengumpulan sampah.

Variabel Luar pada MSDN

Ketika variabel lokal atau parameter nilai ditangkap oleh fungsi anonim, variabel atau parameter lokal tidak lagi dianggap sebagai variabel tetap (variabel tetap dan bergerak), tetapi malah dianggap sebagai variabel yang dapat dipindahkan. Jadi setiap kode tidak aman yang mengambil alamat dari variabel luar yang ditangkap harus terlebih dahulu menggunakan pernyataan tetap untuk memperbaiki variabel. Perhatikan bahwa tidak seperti variabel yang tidak ditangkap, variabel lokal yang ditangkap dapat secara bersamaan diekspos ke beberapa utas eksekusi.

chris hu
sumber