Co to jest zamknięcie ? Czy mamy je w .NET?
Jeśli istnieją w .NET, czy możesz podać fragment kodu (najlepiej w języku C #), który to wyjaśnia?
Mam artykuł na ten temat . (Ma wiele przykładów.)
Zasadniczo zamknięcie to blok kodu, który można wykonać w późniejszym czasie, ale który utrzymuje środowisko, w którym został utworzony po raz pierwszy - tzn. Może nadal używać zmiennych lokalnych itp. Metody, która go utworzyła, nawet po tym metoda zakończyła wykonywanie.
Ogólna funkcja zamknięć jest implementowana w języku C # za pomocą anonimowych metod i wyrażeń lambda.
Oto przykład z wykorzystaniem anonimowej metody:
using System;
class Test
{
static void Main()
{
Action action = CreateAction();
action();
action();
}
static Action CreateAction()
{
int counter = 0;
return delegate
{
// Yes, it could be done in one statement;
// but it is clearer like this.
counter++;
Console.WriteLine("counter={0}", counter);
};
}
}
Wynik:
counter=1
counter=2
Tutaj widzimy, że akcja zwrócona przez CreateAction nadal ma dostęp do zmiennej counter i może ją zwiększyć, nawet jeśli CreateAction zakończyło się.
counter
można zwiększać - kompilator generuje klasę zawierającącounter
pole, a każdy kod odnoszący się docounter
kończy się przechodzeniem przez instancję tej klasy.Jeśli chcesz zobaczyć, jak C # implementuje zamknięcie, przeczytaj: „Znam odpowiedź (jego 42) blog”
Kompilator generuje w tle klasę do enkapsulacji metody anoymicznej i zmiennej j
dla funkcji:
Przekształcając go w:
źródło
Zamknięcia to wartości funkcjonalne, które zachowują wartości zmienne z pierwotnego zakresu. C # może używać ich w formie anonimowych delegatów.
Na bardzo prosty przykład weź ten kod C #:
Na końcu pasek zostanie ustawiony na 4, a delegat myClosure może zostać przekazany do użycia w innym miejscu programu.
Zamknięć można używać do wielu przydatnych rzeczy, takich jak opóźnione wykonanie lub w celu uproszczenia interfejsów - LINQ jest zbudowany głównie z zamknięć. Najbardziej bezpośrednim sposobem, w jaki przydaje się większość programistów, jest dodawanie procedur obsługi zdarzeń do dynamicznie tworzonych kontrolek - możesz użyć zamknięć, aby dodać zachowanie, gdy kontrola jest utworzona, zamiast przechowywać dane w innym miejscu.
źródło
Zamknięcie to anonimowa funkcja przekazana poza funkcję, w której została utworzona. Zachowuje wszelkie zmienne z funkcji, w której zostało utworzone, z której korzysta.
źródło
Oto wymyślony przykład dla C #, który utworzyłem z podobnego kodu w JavaScript:
Oto kod, który pokazuje, jak użyć powyższego kodu ...
Mam nadzieję, że jest to trochę pomocne.
źródło
Zasadniczo zamknięcie to blok kodu, który można przekazać jako argument funkcji. C # obsługuje zamknięcia w formie anonimowych delegatów.
Oto prosty przykład:
Metoda List.Find może zaakceptować i wykonać fragment kodu (zamknięcie) w celu znalezienia elementu listy.
Używając składni C # 3.0 możemy zapisać to jako:
źródło
Zamknięcie ma miejsce, gdy funkcja jest zdefiniowana w innej funkcji (lub metodzie) i wykorzystuje zmienne z metody nadrzędnej . Takie użycie zmiennych, które są zlokalizowane w metodzie i opakowane w zdefiniowaną w niej funkcję, nazywa się zamknięciem.
Mark Seemann ma kilka interesujących przykładów zamknięć w swoim poście na blogu, w którym działa równolegle między programowaniem a programowaniem funkcjonalnym.
I żeby było bardziej szczegółowe
źródło
Zamknięcia to fragmenty kodu, które odwołują się do zmiennej poza sobą (od dołu na stosie), które mogą zostać wywołane lub wykonane później (na przykład, gdy zdefiniowane jest zdarzenie lub delegat, i mogą zostać wywołane w nieokreślonym czasie w przyszłości ) ... Ponieważ zewnętrzna zmienna, do której fragment odwołania do kodu mógł wyjść poza zakres (i w przeciwnym razie zostałby utracony), fakt, że odwołuje się do niego fragment kodu (zwany zamknięciem), mówi środowisku wykonawczemu, aby „wstrzymał” „ta zmienna ma zasięg, dopóki nie będzie już potrzebna przez fragment kodu zamknięcia ...
źródło
Próbowałem to też zrozumieć, poniżej znajdują się fragmenty kodu dla tego samego kodu w JavaScript i C # pokazujące zamknięcie.
JavaScript:
DO#:
JavaScript:
DO#:
źródło
Prosta i bardziej zrozumiała odpowiedź z książki w skrócie C # 7.0.
Wymagania wstępne, które powinieneś wiedzieć : Wyrażenie lambda może odwoływać się do zmiennych lokalnych i parametrów metody, w której jest zdefiniowane (zmienne zewnętrzne).
Część rzeczywista : Zmienne zewnętrzne, do których odwołuje się wyrażenie lambda, nazywane są zmiennymi przechwyconymi. Wyrażenie lambda, które przechwytuje zmienne, nazywa się zamknięciem.
Ostatni punkt, na który należy zwrócić uwagę : Przechwycone zmienne są oceniane podczas rzeczywistego wywołania delegata, a nie podczas przechwytywania zmiennych
źródło
Jeśli napiszesz wbudowaną anonimową metodę (C # 2) lub (najlepiej) wyrażenie Lambda (C # 3 +), nadal tworzona jest faktyczna metoda. Jeśli ten kod używa zmiennej lokalnej o zasięgu zewnętrznym - nadal musisz jakoś przekazać tę zmienną do metody.
np. weź tę klauzulę Linq Where (która jest prostą metodą rozszerzenia, która przekazuje wyrażenie lambda):
jeśli chcesz użyć i w tym wyrażeniu lambda, musisz przekazać ją do utworzonej metody.
Pierwsze pytanie, które się pojawia, brzmi: czy powinno być przekazywane przez wartość czy odniesienie?
Przekazywanie przez referencję jest (jak sądzę) bardziej korzystne, gdy uzyskujesz dostęp do odczytu / zapisu do tej zmiennej (i to właśnie robi C #; Myślę, że zespół w Microsoft rozważał zalety i wady i poszedł z odniesieniem; Według Jona Skeeta artykuł , Java poszedł z wartościami).
Ale pojawia się kolejne pytanie: gdzie to przydzielić?
Czy powinien / powinien być faktycznie / naturalnie przydzielony na stosie? Cóż, jeśli przydzielisz go na stos i przekażesz przez referencję, mogą wystąpić sytuacje, w których przeżyje własną ramkę stosu. Weź ten przykład:
Wyrażenie lambda (w klauzuli Where) ponownie tworzy metodę, która odwołuje się do i. Jeśli i zostanie przydzielone na stosie Outlive, to do czasu wyliczenia whereItems, i użyte w wygenerowanej metodzie wskaże i na Outlive, tj. Miejsce na stosie, które nie jest już dostępne.
Ok, więc potrzebujemy go na stosie.
Więc to, co kompilator C # robi w celu obsługi tego wbudowanego anonimowego / lambda, polega na użyciu tak zwanych „ zamknięć ”: Tworzy klasę na stosie o nazwie ( raczej słabo ) DisplayClass, która zawiera pole zawierające i oraz funkcję, która faktycznie używa to.
Coś, co byłoby równoważne z tym (możesz zobaczyć IL wygenerowaną za pomocą ILSpy lub ILDASM):
Tworzy instancję tej klasy w twoim zasięgu lokalnym i zastępuje każdy kod związany z wyrażeniem i lub lambda tą instancją zamknięcia. Tak więc - za każdym razem, gdy używasz „i” w kodzie „zasięgu lokalnego”, w którym został zdefiniowany, faktycznie używasz tego pola instancji DisplayClass.
Więc jeśli zmienię „lokalne” i w metodzie głównej, faktycznie zmieni ona _DisplayClass.i;
to znaczy
wypisze 12, ponieważ „i = 10” trafia do tego pola disalyclass i zmienia je tuż przed 2. wyliczeniem.
Dobrym źródłem na ten temat jest ten moduł Bart De Smet Pluralsight (wymaga rejestracji) (również zignoruj jego błędne użycie terminu „Podnoszenie” - co (myślę) ma na myśli to, że zmienna lokalna (tj. I) została zmieniona tak, aby odwoływała się do nowego pola DisplayClass).
W innych wiadomościach wydaje się, że istnieje pewne błędne przekonanie, że „Zamknięcia” są powiązane z pętlami - jak rozumiem, „Zamknięcia” NIE są pojęciem związanym z pętlami , ale raczej z użyciem anonimowych metod / wyrażeń lambda z wykorzystaniem zmiennych lokalnych o zasięgu - chociaż pewne sztuczki pytania używają pętli, aby to wykazać.
źródło
Zamknięcie to funkcja zdefiniowana w funkcji, która może uzyskiwać dostęp do lokalnych zmiennych zarówno jej, jak i jej elementu nadrzędnego.
więc funkcja wewnątrz metody find.
może uzyskać dostęp do zmiennych w swoim zakresie, t oraz nazwie zmiennej, która jest w zakresie nadrzędnym. Mimo że jest wykonywany metodą find jako delegat, z innego zakresu razem.
źródło