Доступ до модифікованого закриття


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
Посилання відсутнє
Ерік Ву

Відповіді:


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

З іншого боку, якби ви зберігали, а потім пізніше викликали предикати, ви побачили, що кожен виклик до предикатів дійсно викликав би той самий метод у тому самому екземплярі класу закриття і, отже, використовував би те саме значення для i.


4

"files" - це захоплена зовнішня змінна, оскільки вона була захоплена функцією анонімного делегата. Термін його служби продовжується функцією анонімного делегування.

Захоплені зовнішні змінні Коли на зовнішню змінну посилається анонімна функція, кажуть, що зовнішня змінна була захоплена анонімною функцією. Зазвичай термін служби локальної змінної обмежується виконанням блоку або оператора, з яким він пов'язаний (Локальні змінні). Однак термін служби захопленої зовнішньої змінної подовжується принаймні до тих пір, поки делегат або дерево вираження, створене за допомогою анонімної функції, не стануть придатними для збору сміття.

Зовнішні змінні на MSDN

Коли локальна змінна або параметр значення захоплюється анонімною функцією, локальна змінна чи параметр більше не вважаються фіксованою змінною (Фіксовані та переміщувані змінні), а замість цього вважаються змінною змінною. Таким чином, будь-який небезпечний код, який приймає адресу захопленої зовнішньої змінної, повинен спочатку використовувати фіксований оператор для виправлення змінної. Зауважте, що на відміну від незахопленої змінної, захоплена локальна змінна може бути одночасно піддана дії декількох потоків виконання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.