Zugriff auf Modified Closure

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
}

Das Obige scheint gut zu funktionieren, obwohl ReSharper sich darüber beschwert, dass dies "Zugriff auf modifizierte Schließung" ist. Kann jemand Licht ins Dunkel bringen?

(Dieses Thema wurde hier fortgesetzt )

Vyas Bharghava
quelle
6
Der Link ist aus, aber ich habe ihn bei WebArchive gefunden: web.archive.org/web/20150326104221/http://www.jarloo.com/…
Eric Wu

Antworten:

314

In diesem Fall ist es in Ordnung, da Sie den Delegaten tatsächlich innerhalb der Schleife ausführen .

Wenn Sie den Delegaten speichern und später verwenden, werden Sie jedoch feststellen, dass alle Delegaten beim Versuch, auf Dateien [i] zuzugreifen, Ausnahmen auslösen - sie erfassen die Variable i und nicht ihren Wert zum Zeitpunkt der Delegaten Schaffung.

Kurz gesagt, es ist etwas, das Sie als potenzielle Falle beachten sollten , aber in diesem Fall tut es Ihnen nicht weh.

Am Ende dieser Seite finden Sie ein komplexeres Beispiel, bei dem die Ergebnisse nicht intuitiv sind.

Jon Skeet
quelle
29

Ich weiß, dass dies eine alte Frage ist, aber ich habe kürzlich Verschlüsse untersucht und dachte, ein Codebeispiel könnte nützlich sein. Hinter den Kulissen generiert der Compiler eine Klasse, die einen lexikalischen Abschluss für Ihren Funktionsaufruf darstellt. Es sieht wahrscheinlich so aus:

private sealed class Closure
{
    public string[] files;
    public int i;

    public bool YourAnonymousMethod(string name)
    {
        return name.Equals(this.files[this.i]);
    }
}

Wie oben erwähnt, funktioniert Ihre Funktion, da die Prädikate unmittelbar nach der Erstellung aufgerufen werden. Der Compiler generiert Folgendes:

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

Wenn Sie andererseits die Prädikate speichern und später aufrufen würden, würden Sie sehen, dass jeder einzelne Aufruf der Prädikate tatsächlich dieselbe Methode für dieselbe Instanz der Abschlussklasse aufruft und daher denselben Wert für verwendet ich.

gerrard00
quelle
4

"files" ist eine erfasste äußere Variable, da sie von der anonymen Delegatenfunktion erfasst wurde. Die Lebensdauer wird durch die anonyme Delegatenfunktion verlängert.

Erfasste äußere Variablen Wenn eine äußere Variable von einer anonymen Funktion referenziert wird, wird die äußere Variable von der anonymen Funktion erfasst. Normalerweise ist die Lebensdauer einer lokalen Variablen auf die Ausführung des Blocks oder der Anweisung beschränkt, mit der sie verknüpft ist (lokale Variablen). Die Lebensdauer einer erfassten äußeren Variablen wird jedoch mindestens so lange verlängert, bis der aus der anonymen Funktion erstellte Delegat oder Ausdrucksbaum für die Speicherbereinigung in Frage kommt.

Äußere Variablen auf MSDN

Wenn eine lokale Variable oder ein Wertparameter von einer anonymen Funktion erfasst wird, wird die lokale Variable oder der lokale Parameter nicht mehr als feste Variable (feste und bewegliche Variablen) betrachtet, sondern als bewegliche Variable. Daher muss jeder unsichere Code, der die Adresse einer erfassten äußeren Variablen annimmt, zuerst die feste Anweisung verwenden, um die Variable zu reparieren. Beachten Sie, dass im Gegensatz zu einer nicht erfassten Variablen eine erfasste lokale Variable gleichzeitig mehreren Ausführungsthreads ausgesetzt sein kann.

chris hu
quelle