Czym są "zamknięcia" in.NET?

Co to jest zamknięcie ? Czy mamy je w. NET?

Jeśli istnieją w. NET, czy mógłbyś podać fragment kodu (najlepiej w C#) wyjaśniający to?

 200
Author: Drag and Drop, 2009-01-09

12 answers

Mam artykuł na ten właśnie temat . (Ma wiele przykładów.)

W istocie zamknięcie jest blokiem kodu, który może być wykonany w późniejszym czasie, ale który zachowuje ś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 zakończeniu wykonywania tej metody.

Ogólna cecha zamknięć jest zaimplementowana w C# za pomocą anonimowych metod i wyrażeń lambda.

Oto przykład użycia metody anonimowej:

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

Wyjście:

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 sama CreateAction została zakończona.

 263
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-25 19:20:43

Jeśli interesuje Cię jak C# implementuje Zamknięcie przeczytaj "znam odpowiedź (42) blog"

Kompilator generuje klasę w tle do hermetyzacji metody anoymous i zmiennej j

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
    public <>c__DisplayClass2();
    public void <fillFunc>b__0()
    {
       Console.Write("{0} ", this.j);
    }
    public int j;
}

Dla funkcji:

static void fillFunc(int count) {
    for (int i = 0; i < count; i++)
    {
        int j = i;
        funcArr[i] = delegate()
                     {
                         Console.Write("{0} ", j);
                     };
    } 
}

Przekształcanie go w:

private static void fillFunc(int count)
{
    for (int i = 0; i < count; i++)
    {
        Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
        class1.j = i;
        Program.funcArr[i] = new Func(class1.<fillFunc>b__0);
    }
}
 22
Author: Daniil Grankin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-14 02:17:35

Zamknięcia są wartościami funkcyjnymi, które przechowują wartości zmiennych z ich pierwotnego zakresu. C# może używać ich w formie anonimowych delegatów.

Dla bardzo prostego przykładu, weźmy ten kod C#:

    delegate int testDel();

    static void Main(string[] args)
    {
        int foo = 4;
        testDel myClosure = delegate()
        {
            return foo;
        };
        int bar = myClosure();

    }

Na jego końcu pasek zostanie ustawiony na 4, A delegat myClosure może zostać przekazany do użycia w innym miejscu programu.

Zamknięcia mogą być używane do wielu przydatnych rzeczy, takich jak opóźnione wykonanie lub uproszczenie interfejsów-LINQ jest zbudowany głównie przy użyciu zamknięć. Najpilniejszym sposobem, który jest przydatny dla większości programistów, jest dodawanie procesów obsługi zdarzeń do dynamicznie tworzonych formantów - możesz użyć zamknięć, aby dodać zachowanie, gdy kontrolka jest tworzona jako instancja, zamiast przechowywać dane gdzie indziej.

 12
Author: Dan Monego,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-09 16:17:16
Func<int, int> GetMultiplier(int a)
{
     return delegate(int b) { return a * b; } ;
}
//...
var fn2 = GetMultiplier(2);
var fn3 = GetMultiplier(3);
Console.WriteLine(fn2(2));  //outputs 4
Console.WriteLine(fn2(3));  //outputs 6
Console.WriteLine(fn3(2));  //outputs 6
Console.WriteLine(fn3(3));  //outputs 9

Zamknięcie jest funkcją anonimową przekazywaną poza funkcję, w której została utworzona. Zachowuje wszelkie zmienne z funkcji, w której jest tworzony, że używa.

 10
Author: AnthonyWJones,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-02 12:02:04

Oto wymyślony przykład C#, który stworzyłem z podobnego kodu w JavaScript:

public delegate T Iterator<T>() where T : class;

public Iterator<T> CreateIterator<T>(IList<T> x) where T : class
{
        var i = 0; 
        return delegate { return (i < x.Count) ? x[i++] : null; };
}

Oto kod, który pokazuje jak używać powyższego kodu...

var iterator = CreateIterator(new string[3] { "Foo", "Bar", "Baz"});

// So, although CreateIterator() has been called and returned, the variable 
// "i" within CreateIterator() will live on because of a closure created 
// within that method, so that every time the anonymous delegate returned 
// from it is called (by calling iterator()) it's value will increment.

string currentString;    
currentString = iterator(); // currentString is now "Foo"
currentString = iterator(); // currentString is now "Bar"
currentString = iterator(); // currentString is now "Baz"
currentString = iterator(); // currentString is now null
Mam nadzieję, że to trochę pomocne.
 4
Author: Jason Bunting,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-09 16:12:27

Zamknięcia to fragmenty kodu, które odwołują się do zmiennej poza sobą (spod stosu), które mogą być wywołane lub wykonane później (np. 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 odwołuje się fragment kodu, może wyjść poza zakres (i w przeciwnym razie zostałaby utracona), fakt, że odwołuje się do niej fragment kodu (zwany zamknięciem) nakazuje runtime "przytrzymać" tę zmienną w zakresie, dopóki nie jest już potrzebny przez zamknięcie fragmentu kodu...

 3
Author: Charles Bretana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-09 16:09:24

Zasadniczo closure jest blokiem kodu, który można przekazać jako argument do funkcji. C# obsługuje zamknięcia w formie anonimowych delegatów.

Oto prosty przykład:
Lista.Metoda Find może zaakceptować i wykonać fragment kodu (zamknięcie), aby znaleźć pozycję listy.

// Passing a block of code as a function argument
List<int> ints = new List<int> {1, 2, 3};
ints.Find(delegate(int value) { return value == 1; });

Używając składni C#3.0 możemy zapisać to jako:

ints.Find(value => value == 1);
 2
Author: aku,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-09 16:13:10

Zamknięcie jest wtedy, gdy funkcja jest zdefiniowana wewnątrz innej funkcji (lub metody) i używa zmiennych z metody rodzica . To użycie zmiennych, które znajdują się w metodzie i są zawinięte w zdefiniowaną w niej funkcję, nazywa się zamknięciem.

Mark Seemann ma kilka ciekawych przykładów zamknięć w swoim poście na blogu , gdzie wykonuje paralel pomiędzy oop a programowaniem funkcyjnym.

I aby było bardziej szczegółowe

var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);//when this variable
Func<int, string> read = id =>
    {
        var path = Path.Combine(workingDirectory.FullName, id + ".txt");//is used inside this function
        return File.ReadAllText(path);
    };//the entire process is called a closure.
 2
Author: meJustAndrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-13 09:46:04

Znikąd,prosta i bardziej zrozumiała odpowiedź z książki C# 7.0 nutshell.

Pre-requisit powinieneś wiedzieć: wyrażenie lambda może odwoływać się do lokalnych zmiennych i parametrów metody w którym jest zdefiniowana (zmienne zewnętrzne).

    static void Main()
    {
    int factor = 2;
   //Here factor is the variable that takes part in lambda expression.
    Func<int, int> multiplier = n => n * factor;
    Console.WriteLine (multiplier (3)); // 6
    }

Część rzeczywista : zmienne zewnętrzne, do których odwołuje się wyrażenie lambda, nazywane są zmiennymi przechwytywanymi. Wyrażenie lambda, które przechwytuje zmienne, nazywa się zamknięciem.

Ostatni punkt do noted: przechwycone zmienne są oceniane, gdy delegat jest faktycznie wywoływany, a nie Gdy zmienne zostały przechwycone:

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
 0
Author: Hameed Syed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-23 05:49:49

Jeśli napiszesz anonimową metodę inline (C#2) lub (najlepiej) wyrażenie Lambda (C#3+), rzeczywista metoda jest nadal tworzona. Jeśli ten kod używa zmiennej lokalnej outer-scope - nadal musisz przekazać tę zmienną do metody.

Np. weźmy klauzulę LINQ Where (która jest prostą metodą rozszerzenia przekazującą wyrażenie lambda):

var i = 0;
var items = new List<string>
{
    "Hello","World"
};   
var filtered = items.Where(x =>
// this is a predicate, i.e. a Func<T, bool> written as a lambda expression
// which is still a method actually being created for you in compile time 
{
    i++;
    return true;
});

Jeśli chcesz użyć i w wyrażeniu lambda, musisz przekazać je do tej utworzonej metody.

Więc pierwsze pytanie, które się pojawia, brzmi: czy powinno być przekazywane przez wartość lub odniesienie?

Pass by reference jest (chyba) bardziej preferowane, gdy uzyskasz dostęp do odczytu/zapisu do tej zmiennej (i to właśnie robi C#; myślę, że zespół w Microsofcie zważył zalety i wady i poszedł z by-reference; zgodnie z Jona Skeeta Artykuł, Java poszedł z by-value).

Ale wtedy pojawia się kolejne pytanie: gdzie przydzielić, że ja?

Czy rzeczywiście / naturalnie być przydzielone na stosie? Cóż, jeśli przydzielisz go na stosie i przekażesz przez odniesienie, mogą być sytuacje, w których przetrwa własną ramkę stosu. Weźmy ten przykład:

static void Main(string[] args)
{
    Outlive();
    var list = whereItems.ToList();
    Console.ReadLine();
}

static IEnumerable<string> whereItems;

static void Outlive()
{
    var i = 0;
    var items = new List<string>
    {
        "Hello","World"
    };            
    whereItems = items.Where(x =>
    {
        i++;
        Console.WriteLine(i);
        return true;
    });            
}

Wyrażenie lambda (w klauzuli Where) ponownie tworzy metodę, która odnosi się do i. jeśli i jest przydzielone na stosie Outlive, to do czasu wyliczenia whereItems, i użyte w Wygenerowanej metodzie wskaże i Outlive, tzn. na miejsce w stosie, które nie jest już dostępne.

Ok, więc potrzebujemy go na stosie.

Więc to, co robi kompilator C#, aby wspierać ten inline anonymous / lambda, to użycie czegoś, co nazywa się "Closures": tworzy klasę na stercie o nazwie (raczej słabo) DisplayClass, która ma pole zawierające i i funkcję, która faktycznie go używa.

Coś, co byłoby równoważne temu (możesz zobaczyć IL generowane za pomocą ILSpy lub ILDASM):

class <>c_DisplayClass1
{
    public int i;

    public bool <GetFunc>b__0()
    {
        this.i++;
        Console.WriteLine(i);
        return true;
    }
}

Tworzy instancję tej klasy w Twoim lokalnym scope i zamienia dowolny kod odnoszący się do i lub wyrażenia lambda z tą instancją zamknięcia. Tak więc-za każdym razem, gdy używasz i w swoim kodzie "local scope", w którym zostałem zdefiniowany, w rzeczywistości używasz tego pola instancji DisplayClass.

Więc jeśli zmienię "local" i w głównej metodzie, zmieni to _DisplayClass.i;

Tzn.

var i = 0;
var items = new List<string>
{
    "Hello","World"
};  
var filtered = items.Where(x =>
{
    i++;
    return true;
});
filtered.ToList(); // will enumerate filtered, i = 2
i = 10;            // i will be overwriten with 10
filtered.ToList(); // will enumerate filtered again, i = 12
Console.WriteLine(i); // should print out 12

Wyświetli 12, ponieważ "i = 10" przechodzi do tego pola dispalyclass i zmienia je tuż przed drugim wyliczeniem.

Dobrym źródłem na ten temat jest Bart De Smet Pluralsight module (wymaga rejestracji) (zignoruj również jego błędne użycie terminu "podnoszenie" - co (myślę) ma na myśli, że zmienna lokalna (tj. i) jest zmieniona, aby odnosić się do nowego pola DisplayClass).


W innych wiadomościach pojawia się błędne przekonanie, że "zamknięcia" są związane z pętlami - jak rozumiem "zamknięcia" nie są pojęciem związanym z pętlami , ale raczej z anonimowymi metody / wyrażenia lambda używają zmiennych o zasięgu lokalnym - chociaż niektóre pytania podchwytliwe używają pętli, aby to zademonstrować.

 0
Author: Maverick Meerkat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-13 11:13:18

Zamknięcie ma na celu uproszczenie myślenia Funkcjonalnego i pozwala runtime zarządzać stan, uwalniając dodatkową złożoność dla programisty. Zamknięcie jest funkcją pierwszorzędną z wolnymi zmiennymi, które są związane w środowisku leksykalnym. Behind these buzzwords ukrywa prostą koncepcję: zamknięcia są wygodniejszym sposobem na zapewnienie dostępu do funkcji do stanu lokalnego i do przekazywania danych do operacji w tle. Są to Funkcje specjalne które zawierają implicit wiążące wszystkie zmienne nielokalne (zwane też zmiennymi wolnymi lub up-values). Co więcej, zamknięcie umożliwia funkcji dostęp do jednej lub więcej zmiennych nielokalnych nawet wtedy, gdy jest wywoływana poza jej bezpośrednim zakresem leksykalnym, a ciało tej specjalnej funkcji może przenosić te Zmienne wolne jako jeden byt, zdefiniowany w jego zasięg. Co ważniejsze, zamknięcie zamyka zachowanie i przekazuje je wokół jak każdy inny obiekt, dając dostęp do kontekstu, w którym zamknięcie było tworzenie, odczytywanie i aktualizowanie te wartości.

Tutaj wpisz opis obrazka

 0
Author: mostafa kazemi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-20 07:53:51

Zamknięcie jest funkcją, zdefiniowaną w funkcji, która może uzyskać dostęp do lokalnych zmiennych, jak również do jej rodzica.

public string GetByName(string name)
{
   List<things> theThings = new List<things>();
  return  theThings.Find<things>(t => t.Name == name)[0];
}

Więc funkcja wewnątrz metody find.

 t => t.Name == name

Może uzyskać dostęp do zmiennych wewnątrz zakresu, t i nazwy zmiennej, która znajduje się w zakresie nadrzędnym. Nawet jeśli jest wykonywany metodą find jako delegat, z innego zakresu razem.

 -1
Author: DevelopingChris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-09 16:09:03