Cos'è una chiusura ? Li abbiamo in .NET?
Se esistono in .NET, potresti fornire uno snippet di codice (preferibilmente in C #) che lo spieghi?
Cos'è una chiusura ? Li abbiamo in .NET?
Se esistono in .NET, potresti fornire uno snippet di codice (preferibilmente in C #) che lo spieghi?
Risposte:
Ho un articolo proprio su questo argomento . (Ha molti esempi.)
In sostanza, una chiusura è un blocco di codice che può essere eseguito in un secondo momento, ma che mantiene l'ambiente in cui è stato creato per la prima volta, ovvero può ancora utilizzare le variabili locali ecc. Del metodo che l'ha creato, anche dopo il metodo ha terminato l'esecuzione.
La caratteristica generale delle chiusure è implementata in C # con metodi anonimi ed espressioni lambda.
Ecco un esempio usando un metodo anonimo:
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);
};
}
}
Produzione:
counter=1
counter=2
Qui possiamo vedere che l'azione restituita da CreateAction ha ancora accesso alla variabile counter e può effettivamente incrementarla, anche se CreateAction stessa è terminata.
counter
è disponibile per essere incrementato: il compilatore genera una classe che contiene un counter
campo e qualsiasi codice a cui fa riferimento counter
finisce per passare attraverso un'istanza di quella classe.
Se sei interessato a vedere come C # implementa la chiusura leggi "Conosco il blog della risposta (42)"
Il compilatore genera una classe in background per incapsulare il metodo anoyema e la variabile j
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
per la funzione:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
Trasformandolo in:
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);
}
}
Le chiusure sono valori funzionali che mantengono valori variabili dal loro ambito originale. C # può usarli sotto forma di delegati anonimi.
Per un esempio molto semplice, prendi questo codice C #:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
Al termine, la barra verrà impostata su 4 e il delegato myClosure può essere passato in giro per essere utilizzato altrove nel programma.
Le chiusure possono essere utilizzate per molte cose utili, come l'esecuzione ritardata o per semplificare le interfacce: LINQ è principalmente costruito usando le chiusure. Il modo più immediato che risulta utile per la maggior parte degli sviluppatori è l'aggiunta di gestori di eventi ai controlli creati dinamicamente: è possibile utilizzare le chiusure per aggiungere comportamento quando il controllo viene istanziato, anziché archiviare dati altrove.
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
Una chiusura è una funzione anonima passata al di fuori della funzione in cui viene creata. Mantiene tutte le variabili della funzione in cui è stato creato che utilizza.
Ecco un esempio inventato per C # che ho creato da un codice simile in 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; };
}
Quindi, ecco un po 'di codice che mostra come utilizzare il codice sopra ...
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
Spero che sia in qualche modo utile.
Fondamentalmente la chiusura è un blocco di codice che puoi passare come argomento a una funzione. C # supporta chiusure sotto forma di delegati anonimi.
Ecco un semplice esempio:
Il metodo List.Find può accettare ed eseguire un pezzo di codice (chiusura) per trovare l'elemento dell'elenco.
// 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; });
Usando la sintassi di C # 3.0 possiamo scrivere questo come:
ints.Find(value => value == 1);
Una chiusura è quando una funzione è definita all'interno di un'altra funzione (o metodo) e utilizza le variabili dal metodo parent . Questo uso di variabili che si trovano in un metodo e racchiuso in una funzione definita al suo interno, è chiamato chiusura.
Mark Seemann ha alcuni interessanti esempi di chiusure nel suo post sul blog in cui fa un parallelo tra oop e programmazione funzionale.
E per renderlo più dettagliato
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.
Le chiusure sono blocchi di codice che fanno riferimento a una variabile al di fuori di loro, (da sotto di essi nello stack), che potrebbe essere chiamata o eseguita in un secondo momento (come quando viene definito un evento o un delegato e potrebbe essere chiamata in un determinato momento futuro indefinito ) ... Poiché la variabile esterna a cui il blocco di riferimenti di codice potrebbe non rientrare nell'ambito di applicazione (e che altrimenti sarebbe stato perso), il fatto che sia referenziato dal blocco di codice (chiamato chiusura) dice al runtime di "trattenere "quella variabile nell'ambito fino a quando non è più necessaria dal blocco di chiusura del codice ...
Ho cercato anche di capirlo, ben sotto ci sono gli snippet di codice per lo stesso codice in Javascript e C # che mostrano la chiusura.
JavaScript:
var c = function ()
{
var d = 0;
function inner() {
d++;
alert(d);
}
return inner;
};
var a = c();
var b = c();
<body>
<input type=button value=call onClick="a()"/>
<input type=button value=call onClick="b()"/>
</body>
C #:
using System.IO;
using System;
class Program
{
static void Main()
{
var a = new a();
var b = new a();
a.call();
a.call();
a.call();
b.call();
b.call();
b.call();
}
}
public class a {
int b = 0;
public void call()
{
b++;
Console.WriteLine(b);
}
}
JavaScript:
var c = function ()
{
var d = 0;
function inner() {
d++;
alert(d);
}
return inner;
};
var a = c();
<input type=button value=call onClick="a()"/>
<input type=button value=call onClick="a()"/>
C #:
using System.IO;
using System;
class Program
{
static void Main()
{
var a = new a();
var b = new a();
a.call();
a.call();
a.call();
b.call();
b.call();
b.call();
}
}
public class a {
static int b = 0;
public void call()
{
b++;
Console.WriteLine(b);
}
}
Appena uscito dal nulla, una risposta semplice e comprensiva dal libro C # 7.0 in breve.
Prerequisiti che dovresti conoscere : un'espressione lambda può fare riferimento alle variabili e ai parametri locali del metodo in cui è definita (variabili esterne).
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
}
Parte reale : le variabili esterne a cui fa riferimento un'espressione lambda sono chiamate variabili acquisite. Un'espressione lambda che cattura le variabili è chiamata chiusura.
Ultimo punto da notare : le variabili acquisite vengono valutate quando il delegato viene effettivamente richiamato, non quando le variabili sono state acquisite:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Se si scrive un metodo anonimo in linea (C # 2) o (preferibilmente) un'espressione Lambda (C # 3 +), viene ancora creato un metodo effettivo. Se quel codice utilizza una variabile locale di ambito esterno, è comunque necessario in qualche modo passare quella variabile al metodo.
es. prendi questa clausola Linq Where (che è un semplice metodo di estensione che passa un'espressione 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;
});
se vuoi usare i in quell'espressione lambda, devi passarlo a quel metodo creato.
Quindi la prima domanda che si pone è: dovrebbe essere passato per valore o riferimento?
Il passaggio per riferimento è (immagino) più preferibile quando ottieni l'accesso in lettura / scrittura a quella variabile (e questo è ciò che fa C #; immagino che il team di Microsoft abbia valutato i pro e i contro e abbia seguito il riferimento; Secondo Jon Skeet articolo , Java è andato con per valore).
Ma poi sorge un'altra domanda: dove assegnare che io?
Dovrebbe essere effettivamente / naturalmente allocato in pila? Bene, se lo allocate nello stack e lo passate per riferimento, ci possono essere situazioni in cui sopravvive al proprio frame dello stack. Prendi questo esempio:
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;
});
}
L'espressione lambda (nella clausola Where) crea nuovamente un metodo che fa riferimento a una i. Se i è allocato sullo stack di Outlive, quando si enumerano whereItems, l'i utilizzato nel metodo generato punterà all'i di Outlive, ovvero a un punto dello stack che non è più accessibile.
Ok, allora ne abbiamo bisogno sul mucchio.
Quindi, ciò che il compilatore C # fa per supportare questo inline anonymous / lambda, è usare ciò che viene chiamato " Closures ": crea una classe sull'heap chiamata ( piuttosto male ) DisplayClass che ha un campo contenente l'i e la funzione che effettivamente utilizza esso.
Qualcosa che sarebbe equivalente a questo (puoi vedere l'IL generato usando ILSpy o ILDASM):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
Crea un'istanza di quella classe nell'ambito locale e sostituisce qualsiasi codice relativo a i o all'espressione lambda con quell'istanza di chiusura. Quindi, ogni volta che si utilizza l'i nel codice "ambito locale" in cui sono stato definito, si sta effettivamente utilizzando quel campo di istanza DisplayClass.
Quindi, se cambierei l'i "locale" nel metodo principale, cambierebbe effettivamente _DisplayClass.i;
vale a dire
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
verrà stampato 12, poiché "i = 10" va a quel campo di dispalyclass e lo cambia appena prima della seconda enumerazione.
Una buona fonte sull'argomento è questo modulo di Pluralsight di Bart De Smet (richiede la registrazione) (ignora anche il suo uso errato del termine "Hoisting" - ciò che (penso) intende che è che la variabile locale (cioè i) è cambiata per fare riferimento nel nuovo campo DisplayClass).
In altre notizie, sembra esserci un malinteso secondo cui le "chiusure" sono correlate ai loop - come ho capito, le "chiusure" NON sono un concetto correlato ai loop , ma piuttosto a metodi anonimi / espressioni lambda uso di variabili con ambito locale - sebbene qualche trucco le domande usano i loop per dimostrarlo.
Una chiusura è una funzione, definita all'interno di una funzione, che può accedere alle variabili locali di essa e al suo genitore.
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
quindi la funzione all'interno del metodo find.
t => t.Name == name
può accedere alle variabili all'interno del suo ambito, t, e il nome della variabile che si trova nel suo ambito padre. Anche se viene eseguito dal metodo find come delegato, da un altro ambito tutti insieme.