Доступ к измененному закрытию

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

С другой стороны, если бы вы сохранили, а затем вызвали предикаты, вы бы увидели, что каждый отдельный вызов предикатов действительно будет вызывать один и тот же метод в одном и том же экземпляре класса замыкания и, следовательно, будет использовать то же значение для я.

gerrard00
источник
4

«files» - это захваченная внешняя переменная, потому что она была захвачена анонимной функцией делегата. Срок его службы продлевается функцией анонимного делегата.

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

Внешние переменные в MSDN

Когда локальная переменная или параметр значения захватывается анонимной функцией, локальная переменная или параметр больше не считается фиксированной переменной (фиксированные и подвижные переменные), а вместо этого считается подвижной переменной. Таким образом, любой небезопасный код, который принимает адрес захваченной внешней переменной, должен сначала использовать оператор fixed для исправления переменной. Обратите внимание, что в отличие от неперехваченной переменной, захваченная локальная переменная может быть одновременно доступна нескольким потокам выполнения.

Крис Ху
источник