Was ist eine Schließung ? Haben wir sie in .NET?
Wenn sie in .NET vorhanden sind, können Sie bitte ein Code-Snippet (vorzugsweise in C #) bereitstellen, das dies erklärt.
Was ist eine Schließung ? Haben wir sie in .NET?
Wenn sie in .NET vorhanden sind, können Sie bitte ein Code-Snippet (vorzugsweise in C #) bereitstellen, das dies erklärt.
Antworten:
Ich habe einen Artikel zu diesem Thema . (Es gibt viele Beispiele.)
Im Wesentlichen ist ein Abschluss ein Codeblock, der zu einem späteren Zeitpunkt ausgeführt werden kann, aber die Umgebung beibehält, in der er zuerst erstellt wurde - dh er kann auch danach noch die lokalen Variablen usw. der Methode verwenden, die ihn erstellt hat Die Ausführung der Methode ist beendet.
Die allgemeine Funktion von Closures wird in C # durch anonyme Methoden und Lambda-Ausdrücke implementiert.
Hier ist ein Beispiel mit einer anonymen Methode:
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);
};
}
}
Ausgabe:
counter=1
counter=2
Hier können wir sehen, dass die von CreateAction zurückgegebene Aktion weiterhin Zugriff auf die Zählervariable hat und diese tatsächlich erhöhen kann, obwohl CreateAction selbst abgeschlossen ist.
counter
kann inkrementiert werden. Der Compiler generiert eine Klasse, die ein counter
Feld enthält , und jeder Code, auf den counter
verwiesen wird, durchläuft eine Instanz dieser Klasse.
Wenn Sie sehen möchten, wie C # Closure implementiert, lesen Sie "Ich kenne die Antwort (sein 42) Blog".
Der Compiler generiert im Hintergrund eine Klasse, um die anoymöse Methode und die Variable j zu kapseln
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
für die Funktion:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
Verwandeln Sie es 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);
}
}
Closures sind Funktionswerte, die variable Werte aus ihrem ursprünglichen Bereich beibehalten. C # kann sie in Form von anonymen Delegierten verwenden.
Nehmen Sie für ein sehr einfaches Beispiel diesen C # -Code:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
Am Ende wird die Leiste auf 4 gesetzt, und der myClosure-Delegat kann weitergegeben werden, um an anderer Stelle im Programm verwendet zu werden.
Closures können für viele nützliche Dinge verwendet werden, z. B. für die verzögerte Ausführung oder zur Vereinfachung von Schnittstellen. LINQ wird hauptsächlich mit Closures erstellt. Für die meisten Entwickler ist es am unmittelbarsten, Ereignisbehandlungsroutinen zu dynamisch erstellten Steuerelementen hinzuzufügen. Sie können Abschlüsse verwenden, um Verhalten hinzuzufügen, wenn das Steuerelement instanziiert wird, anstatt Daten an anderer Stelle zu speichern.
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
Ein Abschluss ist eine anonyme Funktion, die außerhalb der Funktion übergeben wird, in der er erstellt wurde. Es verwaltet alle Variablen aus der Funktion, in der es erstellt wird, die es verwendet.
Hier ist ein erfundenes Beispiel für C #, das ich aus ähnlichem Code in JavaScript erstellt habe:
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; };
}
Hier ist ein Code, der zeigt, wie der obige Code verwendet wird ...
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
Hoffe das ist etwas hilfreich.
Grundsätzlich ist Closure ein Codeblock, den Sie als Argument an eine Funktion übergeben können. C # unterstützt Schließungen in Form von anonymen Delegierten.
Hier ist ein einfaches Beispiel: Die
List.Find-Methode kann Code akzeptieren und ausführen (Abschluss), um das Element der Liste zu finden.
// 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; });
Mit der C # 3.0-Syntax können wir Folgendes schreiben:
ints.Find(value => value == 1);
Ein Abschluss liegt vor, wenn eine Funktion in einer anderen Funktion (oder Methode) definiert ist und die Variablen der übergeordneten Methode verwendet . Diese Verwendung von Variablen, die sich in einer Methode befinden und in eine darin definierte Funktion eingeschlossen sind, wird als Abschluss bezeichnet.
Mark Seemann hat einige interessante Beispiele für Schließungen in seinem Blogbeitrag , wo er hat eine paralel zwischen oop und funktionalen Programmierung.
Und um es detaillierter zu machen
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.
Closures sind Codestücke, die auf eine Variable außerhalb von sich selbst verweisen (von unten auf dem Stapel), die möglicherweise später aufgerufen oder ausgeführt werden (z. B. wenn ein Ereignis oder ein Delegat definiert ist und zu einem unbestimmten zukünftigen Zeitpunkt aufgerufen werden kann) ) ... Da die externe Variable, auf die der Codeblock verweist, möglicherweise nicht mehr gültig ist (und sonst verloren gegangen wäre), weist die Tatsache, dass der Codeblock (als Closure bezeichnet) auf ihn verweist, die Laufzeit an, "zu halten" "Diese Variable im Gültigkeitsbereich, bis sie vom Abschlusscode-Code nicht mehr benötigt wird ...
Ich habe versucht, es auch zu verstehen. Weiter unten finden Sie die Codefragmente für Same Code in Javascript und C #, die den Abschluss anzeigen.
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);
}
}
Aus heiterem Himmel eine einfache und verständnisvollere Antwort aus dem Buch C # 7.0 auf den Punkt gebracht.
Voraussetzung, die Sie kennen sollten : Ein Lambda-Ausdruck kann auf die lokalen Variablen und Parameter der Methode verweisen, in der er definiert ist (äußere Variablen).
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
}
Realteil : Äußere Variablen, auf die ein Lambda-Ausdruck verweist, werden als erfasste Variablen bezeichnet. Ein Lambda-Ausdruck, der Variablen erfasst, wird als Abschluss bezeichnet.
Letzter zu beachtender Punkt : Erfasste Variablen werden ausgewertet, wenn der Delegat tatsächlich aufgerufen wird, nicht, wenn die Variablen erfasst wurden:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Wenn Sie eine anonyme Inline-Methode (C # 2) oder (vorzugsweise) einen Lambda-Ausdruck (C # 3 +) schreiben, wird noch eine tatsächliche Methode erstellt. Wenn dieser Code eine lokale Variable im äußeren Bereich verwendet, müssen Sie diese Variable trotzdem irgendwie an die Methode übergeben.
Nehmen Sie z. B. diese Linq Where-Klausel (eine einfache Erweiterungsmethode, die einen Lambda-Ausdruck übergibt):
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;
});
Wenn Sie i in diesem Lambda-Ausdruck verwenden möchten, müssen Sie es an diese erstellte Methode übergeben.
Die erste Frage, die sich stellt, lautet: Soll sie als Wert oder Referenz übergeben werden?
Referenzübergabe ist (ich denke) vorzuziehen, da Sie Lese- / Schreibzugriff auf diese Variable erhalten (und dies ist, was C # tut; ich denke, das Team in Microsoft hat die Vor- und Nachteile abgewogen und sich für Referenzangaben entschieden; laut Jon Skeet Artikel , Java ging mit By-Value).
Aber dann stellt sich eine andere Frage: Wo soll ich das zuordnen?
Sollte es tatsächlich / natürlich auf dem Stapel zugeordnet werden? Wenn Sie es dem Stapel zuweisen und als Referenz übergeben, kann es Situationen geben, in denen es seinen eigenen Stapelrahmen überlebt. Nehmen Sie dieses Beispiel:
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;
});
}
Der Lambda-Ausdruck (in der Where-Klausel) erstellt erneut eine Methode, die auf ein i verweist. Wenn i auf dem Stapel von Outlive zugewiesen ist, zeigt das in der generierten Methode verwendete i zum Zeitpunkt der Aufzählung der whereItems auf das i von Outlive, dh auf eine Stelle im Stapel, auf die nicht mehr zugegriffen werden kann.
Ok, dann brauchen wir es auf dem Haufen.
Der C # -Compiler unterstützt dieses Inline-Anonym / Lambda mit der Bezeichnung " Closures ": Er erstellt auf dem Heap eine Klasse namens ( ziemlich schlecht ) DisplayClass, die ein Feld enthält, das das i und die tatsächlich verwendete Funktion enthält es.
Etwas, das dem äquivalent wäre (Sie können die mit ILSpy oder ILDASM generierte IL sehen):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
Es instanziiert diese Klasse in Ihrem lokalen Bereich und ersetzt jeden Code, der sich auf i oder den Lambda-Ausdruck bezieht, durch diese Abschlussinstanz. Wenn Sie also das i in Ihrem "lokalen Bereich" -Code verwenden, in dem ich definiert wurde, verwenden Sie tatsächlich dieses DisplayClass-Instanzfeld.
Wenn ich also das "lokale" i in der Hauptmethode ändern würde, würde es tatsächlich _DisplayClass.i ändern;
dh
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
es wird 12 ausgedruckt, wenn "i = 10" zu diesem Disclalyclass-Feld geht und es kurz vor der 2. Aufzählung ändert.
Eine gute Quelle zu diesem Thema ist dieses Bart De Smet Pluralsight-Modul (Registrierung erforderlich) (ignorieren Sie auch seine fehlerhafte Verwendung des Begriffs "Heben" - was (ich denke) bedeutet, dass die lokale Variable (dh i) geändert wird, um darauf zu verweisen in das neue DisplayClass-Feld).
In anderen Nachrichten scheint es ein Missverständnis zu geben, dass "Closures" mit Schleifen zusammenhängen - wie ich verstehe, sind "Closures" KEIN Konzept, das sich auf Schleifen bezieht , sondern auf anonyme Methoden / Lambda-Ausdrücke, die Variablen mit lokalem Gültigkeitsbereich verwenden - obwohl dies ein Trick ist Fragen verwenden Schleifen, um dies zu demonstrieren.
Ein Abschluss ist eine Funktion, die innerhalb einer Funktion definiert ist und auf die lokalen Variablen sowie auf deren übergeordnete Funktion zugreifen kann.
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
also die Funktion innerhalb der find-Methode.
t => t.Name == name
kann auf die Variablen in seinem Bereich t und auf den Variablennamen zugreifen, der sich in seinem übergeordneten Bereich befindet. Obwohl es von der find-Methode als Delegat ausgeführt wird, aus einem anderen Bereich insgesamt.