Dostęp do zmodyfikowanego zamknięcia

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
}

Powyższe wydaje się działać dobrze, chociaż ReSharper narzeka, że ​​jest to „dostęp do zmodyfikowanego zamknięcia”. Czy ktoś może rzucić na to światło?

(ten temat kontynuowano tutaj )

Vyas Bharghava
źródło
6
Link jest niedostępny, ale znalazłem go na stronie WebArchive: web.archive.org/web/20150326104221/http://www.jarloo.com/…
Eric Wu

Odpowiedzi:

314

W takim przypadku jest to w porządku, ponieważ faktycznie wykonujesz delegata w pętli.

Gdybyś jednak zapisywał delegata i używał go później, okazałoby się, że wszyscy delegaci rzuciliby wyjątki podczas próby uzyskania dostępu do plików [i] - przechwytują zmienną, i a nie jej wartość w momencie delegowania kreacja.

Krótko mówiąc, jest to coś, o czym należy pamiętać jako potencjalna pułapka, ale w tym przypadku nie zaszkodzi ci to.

Zobacz na dole tej strony bardziej złożony przykład, w którym wyniki są sprzeczne z intuicją.

Jon Skeet
źródło
29

Wiem, że to stare pytanie, ale ostatnio studiowałem zamknięcia i pomyślałem, że próbka kodu może być przydatna. Za kulisami kompilator generuje klasę, która reprezentuje leksykalne zamknięcie twojego wywołania funkcji. Prawdopodobnie wygląda to tak:

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

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

Jak wspomniano powyżej, funkcja działa, ponieważ predykaty są wywoływane natychmiast po utworzeniu. Kompilator wygeneruje coś takiego:

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

Z drugiej strony, jeśli miałbyś zapisać, a następnie wywołać predykaty, zobaczyłbyś, że każde pojedyncze wywołanie predykatów naprawdę wywoływałoby tę samą metodę w tej samej instancji klasy zamknięcia, a zatem użyłby tej samej wartości dla ja.

gerrard00
źródło
4

„pliki” to przechwycona zmienna zewnętrzna, ponieważ została przechwycona przez anonimową funkcję delegowania. Jego żywotność jest przedłużana przez anonimową funkcję delegowania.

Przechwycone zmienne zewnętrzne Kiedy do zmiennej zewnętrznej odwołuje się funkcja anonimowa, mówi się, że zmienna zewnętrzna została przechwycona przez funkcję anonimową. Zwykle czas życia zmiennej lokalnej jest ograniczony do wykonania bloku lub instrukcji, z którą jest on powiązany (zmienne lokalne). Jednak czas życia przechwyconej zmiennej zewnętrznej jest przedłużany co najmniej do momentu, gdy drzewo delegowane lub drzewo wyrażeń utworzone z funkcji anonimowej stanie się uprawnione do odśmiecania.

Zmienne zewnętrzne w MSDN

Kiedy zmienna lokalna lub parametr wartości jest przechwytywana przez funkcję anonimową, zmienna lokalna lub parametr nie jest już uważany za zmienną stałą (zmienną stałą i ruchomą), ale jest zamiast tego uważany za zmienną ruchomą. Dlatego każdy niebezpieczny kod, który pobiera adres przechwyconej zmiennej zewnętrznej, musi najpierw użyć instrukcji fixed, aby naprawić zmienną. Zauważ, że w przeciwieństwie do niezapisanej zmiennej, przechwycona zmienna lokalna może być jednocześnie narażona na wiele wątków wykonania.

Chris hu
źródło