Доступ к модифицированному закрытию

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 жалуется, что это "доступ к модифицированному закрытию". Может ли кто-нибудь пролить свет на это?

(эта тема продолжалась здесь)

Ответ 1

В этом случае это нормально, поскольку вы фактически выполняете делегат в цикле.

Однако если вы сохранили делегат и используете его позже, вы обнаружите, что все делегаты будут бросать исключения при попытке доступа к файлам [i] - они захватывают переменную i, а не ее значение во время создания делегатов.

Короче говоря, это то, что нужно знать как потенциальную ловушку, но в этом случае это не повредит вам.

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

Ответ 2

Я знаю, что это старый вопрос, но я недавно изучал закрытие и думал, что пример кода может быть полезен. За кулисами компилятор генерирует класс, который представляет собой лексическое закрытие для вызова функции. Вероятно, это выглядит примерно так:

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.

Ответ 3

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

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

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

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