Închideri (closure)
O închidere (closure) reprezintă un obiect funcție care memorează mediul său lexical chiar și atunci când este executată în afara domeniului său de vizibilitate.
Tehnic, o închidere include trei componente:
- Funcția externă, care definește un anumit domeniu de vizibilitate și în care sunt definite unele variabile și parametri - mediul lexical
- Variabilele și parametrii (mediul lexical), care sunt definite în funcția externă
- Funcția internă, care utilizează variabilele și parametrii funcției externe
În limbajul C#, închiderile pot fi realizate în diferite moduri - cu ajutorul funcțiilor locale și expresiilor lambda.
Să analizăm crearea închiderilor prin funcții locale:
var fn = Outer(); // fn = Inner, deoarece metoda Outer returnează funcția Inner
// apelăm funcția internă Inner
fn(); // 6
fn(); // 7
fn(); // 8
Action Outer() // metodă sau funcție externă
{
int x = 5; // mediu lexical - variabilă locală
void Inner() // funcție locală
{
x++; // operații cu mediul lexical
Console.WriteLine(x);
}
return Inner; // returnăm funcția locală
}
Aici, metoda Outer are ca tip de returnare tipul Action, adică metoda returnează o funcție care nu acceptă parametri și are tipul void.
Action Outer()
În interiorul metodei Outer este definită variabila x - aceasta este mediul lexical pentru funcția internă:
int x = 5;
De asemenea, în interiorul metodei Outer este definită funcția internă - funcția locală Inner, care accesează mediul său lexical - variabila x - îi mărește valoarea cu unu și o afișează în consolă:
void Inner()
{
x++;
Console.WriteLine(x);
}
Această funcție locală este returnată de metoda Outer:
return Inner;
În program, apelăm metoda Outer și obținem în variabila fn funcția locală Inner:
var fn = Outer();
Variabila fn reprezintă o închidere, adică combină două lucruri: funcția și mediul în care funcția a fost creată. Și, deși am obținut funcția locală și o putem apela în afara metodei în care a fost definită, aceasta își amintește mediul său lexical și poate să-l acceseze și să-l modifice, ceea ce vedem prin ieșirea în consolă:
fn(); // 6
fn(); // 7
fn(); // 8
Realizarea cu ajutorul expresiilor lambda
Cu ajutorul lambdelor putem simplifica definirea unei închideri:
var outerFn = () =>
{
int x = 10;
var innerFn = () => Console.WriteLine(++x);
return innerFn;
};
var fn = outerFn(); // fn = innerFn, deoarece outerFn returnează innerFn
// apelăm innerFn
fn(); // 11
fn(); // 12
fn(); // 13
Utilizarea parametrilor
Pe lângă variabilele externe, mediul lexical include și parametrii metodei înconjurătoare. Să analizăm utilizarea parametrilor:
var fn = Multiply(5);
Console.WriteLine(fn(5)); // 25
Console.WriteLine(fn(6)); // 30
Console.WriteLine(fn(7)); // 35
Operation Multiply(int n)
{
int Inner(int m)
{
return n * m;
}
return Inner;
}
delegate int Operation(int n);
Aici, funcția externă - metoda Multiply returnează o funcție care acceptă un număr de tip int și returnează un număr de tip int. Pentru aceasta este definit delegatul Operation, care va reprezenta tipul returnat:
delegate int Operation(int n);
Deși am putea folosi și delegatul încorporat Func<int, int>.
Apelul metodei Multiply() returnează funcția locală care corespunde semnăturii delegatului Operation:
int Inner(int m)
{
return n * m;
}
Această funcție își amintește mediul în care a fost creată, în special valoarea parametrului n. În plus, acceptă un parametru și returnează produsul parametrilor n și m.
În final, la apelul metodei Multiply, se definește variabila fn, care primește funcția locală Inner și mediul său lexical - valoarea parametrului n:
var fn = Multiply(5);
În acest caz, parametrul n este egal cu 5.
La apelul funcției locale, de exemplu, în cazul:
Console.WriteLine(fn(6)); // 30
Numărul 6 este transmis pentru parametrul m al funcției locale, care returnează produsul n și m, adică 5 * 6 = 30.
De asemenea, putem simplifica tot acest cod cu ajutorul lambdelor:
var multiply = (int n) => (int m) => n * m;
var fn = multiply(5);
Console.WriteLine(fn(5)); // 25
Console.WriteLine(fn(6)); // 30
Console.WriteLine(fn(7)); // 35