Доступ к измененному закрытию
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 ответа
В этом случае все в порядке, так как вы фактически выполняете делегат в цикле.
Однако если вы сохраните делегат и будете использовать его позже, вы обнаружите, что все делегаты будут выдавать исключения при попытке доступа к файлам [i] - они захватывают переменную i
а не его значение во время создания делегатов.
Короче говоря, это то, что нужно осознавать как потенциальную ловушку, но в этом случае это не повредит вам.
В нижней части этой страницы приведен более сложный пример, в котором результаты противоречивы.
Я знаю, что это старый вопрос, но недавно я изучал замыкания и подумал, что пример кода может быть полезен. За кулисами компилятор генерирует класс, который представляет лексическое замыкание для вашего вызова функции. Это, вероятно, выглядит примерно так:
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;
}
С другой стороны, если бы вы сохранили, а затем вызвали предикаты, вы бы увидели, что каждый отдельный вызов предикатов действительно будет вызывать один и тот же метод в одном и том же экземпляре класса замыкания и, следовательно, будет использовать то же значение для я.
"files" - это захваченная внешняя переменная, потому что она была захвачена анонимной функцией делегата. Время его жизни увеличивается с помощью анонимной функции делегата.
Захваченные внешние переменные Когда на внешнюю переменную ссылается анонимная функция, считается, что внешняя переменная была захвачена анонимной функцией. Обычно время жизни локальной переменной ограничено выполнением блока или оператора, с которым она связана (локальные переменные). Однако время жизни захваченной внешней переменной увеличивается, по крайней мере, до тех пор, пока дерево делегатов или выражений, созданное из анонимной функции, не станет пригодным для сбора мусора.
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions
Когда локальная переменная или параметр значения фиксируется анонимной функцией, локальная переменная или параметр больше не считается фиксированной переменной (фиксированные и подвижные переменные), а вместо этого считается подвижной переменной. Таким образом, любой небезопасный код, который получает адрес захваченной внешней переменной, должен сначала использовать оператор fixed для исправления переменной. Обратите внимание, что в отличие от неперехваченной переменной, захваченная локальная переменная может быть одновременно доступна нескольким потокам выполнения.