Доступ к измененному закрытию - PullRequest
304 голосов
/ 25 октября 2008
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 жалуется, что это «доступ к измененному закрытию». Кто-нибудь может пролить свет на это?

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

Ответы [ 3 ]

305 голосов
/ 25 октября 2008

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

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

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

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

27 голосов
/ 29 марта 2013

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

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.

2 голосов
/ 14 августа 2017

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

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

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#outer-variables

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

...