Ich verstehe, dass Delegaten Methodenaufrufe kapseln. Aber ich habe Schwierigkeiten, ihre Bedürfnisse zu verstehen. Warum überhaupt Delegierte verwenden, für welche Situationen sind sie gedacht?
Ein Delegat ist im Grunde ein Methodenzeiger. Ein Delegat lässt uns eine Referenzvariable erstellen, aber statt auf eine Instanz einer Klasse zu verweisen, verweist sie auf eine Methode innerhalb der Klasse. Es verweist auf jede Methode, die einen Rückgabetyp und dieselben Parameter wie von diesem Delegat angegeben hat. Es ist ein sehr nützlicher Aspekt der Veranstaltung. Zum gründlichen Lesen empfehle ich Ihnen, das Thema in Head First C # (von Andrew Stellman und Jennifer Greene) zu lesen. Es erklärt schön das Thema des Delegates sowie die meisten Konzepte in .NET.
Nun, einige häufige Anwendungen:
Denken Sie nicht daran, dass Methodenaufrufe eingekapselt werden. Stellen Sie sich vor, dass sie ein beliebiges Bit Verhalten / Logik mit einer bestimmten Signatur kapseln. Der Teil "Methode" ist irrelevant.
Eine andere Art, einen Delegattyp zu denken, ist eine einzige Methodenschnittstelle. Ein gutes Beispiel hierfür ist die IComparer<T>
Schnittstelle und ihr Dual, die Comparison<T>
Delegattyp. Sie repräsentieren die gleiche Grundidee; Manchmal ist es einfacher, dies als Delegierter auszudrücken, und manchmal erleichtert eine Schnittstelle das Leben. (Sie können natürlich einfach Code schreiben, um zwischen den beiden zu konvertieren.)
Sie sind sehr weit ausgelegt, wenn Sie Code haben, von dem Sie wissen, dass er anderen Code aufrufen muss - aber Sie wissen zur Kompilierzeit nicht, was dieser andere Code sein könnte.
Stellen Sie sich zum Beispiel das Windows Forms Button.Click-Ereignis vor, das einen Delegaten verwendet. Die Windows Forms-Programmierer wissen, dass etwas passieren soll, wenn diese Schaltfläche gedrückt wird, aber sie haben keine Möglichkeit, genau zu wissen, was Sie tun wollen ... es könnte alles Mögliche sein!
Sie erstellen also eine Methode und weisen sie einem Delegaten zu und legen sie für dieses Ereignis fest, und schon sind Sie da. Das ist die grundlegende Argumentation für die Delegierten, obwohl es viele andere gute Verwendungen für sie gibt, die verwandt sind.
Delegierte werden oft für Events
verwendet. Laut MSDN sind Delegaten in .NET für Folgendes konzipiert:
- Ein Evening Design Pattern wird verwendet.
- Es ist wünschenswert, eine statische Methode zu kapseln.
- Der Aufrufer muss nicht auf andere Eigenschaften, Methoden oder Schnittstellen zugreifen das Objekt, das die Methode implementiert.
- Einfache Zusammensetzung ist erwünscht.
- Eine Klasse benötigt möglicherweise mehr als eine Implementierung der Methodenimplementierung der Methode
Noch eine gute Erklärung von MSDN,
Ein gutes Beispiel für die Verwendung eines Single-Methode-Schnittstelle anstelle von a Delegat ist
IComparable
oder %Code%.IComparable
deklariert die CompareTo-Methode, die ein Ganzzahl, die a weniger als, gleich zu oder größer als Beziehung zwischen zwei Objekten desselben Typs. IComparable kann als Basis verwendet werden eines Sortieralgorithmus und während der Verwendung von a Delegate Vergleichsmethode als die Grundlage eines Sortieralgorithmus wäre gültig, es ist nicht ideal. Weil das Vergleichbarkeit gehört zum Klasse und der Vergleichsalgorithmus ändert sich zur Laufzeit nicht, a Single-Methode-Schnittstelle ist ideal. Single-Methode-Schnittstelle ist ideal.
Seit .NET 2.0 wurde es auch für anonyme Funktionen .
Wikipedia hat eine schöne Erklärung zum Delegationsmuster,
In der Softwareentwicklung ist das Delegierungsmuster ein Entwurfsmuster in der objektorientierten Programmierung, bei dem ein Objekt diese Aufgabe an ein zugehöriges Hilfsobjekt delegiert, anstatt eine der angegebenen Aufgaben auszuführen. Es übergibt sozusagen den Bock (technisch eine Inversion of Responsibility). Das Hilfsobjekt heißt Delegat. Das Delegationsmuster ist eines der grundlegenden Abstraktionsmuster, die anderen Softwaremustern wie Komposition (auch als Aggregation bezeichnet), Mixins und Aspekten zugrunde liegen.
Übervereinfacht: Ich würde sagen, dass ein Delegat ein Platzhalter für eine Funktion ist, bis zu dem Zeitpunkt, an dem dem Delegierten eine echte Funktion zugewiesen wird. Der Aufruf von nicht zugewiesenen Delegaten löst eine Ausnahme aus.
Verwirrung tritt auf, weil zwischen der Definition, Deklaration, Instanziierung und dem Aufruf von Delegierten oft ein kleiner Unterschied besteht.
Definition:
Fügen Sie dies in einen Namensraum ein, wie Sie es bei jeder Klassendefinition tun würden.
Dies ist vergleichbar mit einer Klassendefinition, in der Sie jetzt Variablen dieses Delegaten deklarieren können.
Erklärung:
Setzen Sie das ist eine der Funktionsroutinen wie Sie jede Variable deklarieren würden.
Instantiierung und Zuweisung:
Normalerweise machst du das zusammen mit der Deklaration.
Das "neue DoSomething (..)" ist die Instanziierung. Die doSth = ... ist die Aufgabe.
Beachten Sie, dass Sie bereits eine Funktion namens "MyDoSomething" definiert haben, die eine Zeichenfolge akzeptiert und ein Bool zurückgibt.
Dann können Sie die Funktion aufrufen.
Aufruf:
%Vor% Ereignisse:
Sie können sehen, wo Ereignisse eintreten:
Da ein Mitglied einer Klasse normalerweise eine Deklaration ist, die auf einer Definition basiert.
Wie
Ein Ereignis ist eine Deklaration, die auf einem Delegaten basiert:
%Vor%Der Unterschied zum vorherigen Beispiel ist, dass Ereignisse "besonders" sind:
Sie sind wirklich syntaktischer Zucker für Arrays von Delegierten.
Der Punkt ist natürlich, dass etwas / jemand anderes das Zuweisen für Sie übernimmt.
Delegaten ermöglichen Ihnen, einen Verweis auf eine Methode zu übergeben. Ein bekanntes Beispiel ist das Übergeben einer Vergleichsmethode an eine Sortierfunktion.
Wenn Sie zur Laufzeit entscheiden müssen, welche Methode Sie aufrufen möchten, verwenden Sie einen Delegaten. Der Delegat wird dann zur Laufzeit auf eine Aktion / ein Ereignis reagieren und die entsprechende Methode aufrufen. Es ist so, als würde man einen "Delegierten" zu einer Hochzeit schicken, an der man selbst nicht teilnehmen möchte: -)
Die C-Leute werden dies als Funktionszeiger erkennen, aber lassen Sie sich hier nicht in die Terminologie verstricken. Alle Delegaten tun dies (und es ist tatsächlich ein Typ), stellt die Signatur der Methode bereit, die später aufgerufen wird, um die entsprechende Logik zu implementieren.
Das Buch "Illustrated C #" von Dan Solis bietet den einfachsten Einstieg für das Erlernen dieses Konzepts, auf das ich gestoßen bin:
Ein Delegat ist in der Regel eine Kombination aus einer Objektreferenz und einem Zeiger auf eine der Klassenmethoden des Objekts (Delegaten können für statische Methoden erstellt werden; in diesem Fall gibt es keine Objektreferenz). Delegierte können ohne Berücksichtigung des Typs des eingeschlossenen Objekts aufgerufen werden, da der eingeschlossene Methodenzeiger für das enthaltene Objekt garantiert gültig ist.
Um etwas über die Nützlichkeit der Delegierten zu verstehen, sollten Sie sich an die Sprache C und die printf "Familie" der Funktionen in C erinnern. Angenommen, Sie wollten eine universelle Version von "printf", die nicht nur verwendet werden kann B. printf, fprintf, sprintf usw., könnte aber seine Ausgabe an einen seriellen Port, eine Textbox, einen TCP-Port, eine Cookie-Frosting-Maschine oder was auch immer senden, ohne einen Puffer vorab reservieren zu müssen. Natürlich müsste eine solche Funktion einen Funktionszeiger für die Zeichenausgaberoutine akzeptieren, aber das wäre im Allgemeinen nicht ausreichend.
Eine typische Implementierung (leider nicht standardisiert) wird eine allgemeine gp_printf-Routine haben, die (zusätzlich zu der Formatzeichenfolge und Ausgabeparametern) einen void-Zeiger und einen Zeiger auf eine Funktion akzeptiert, die ein Zeichen und einen void-Zeiger akzeptiert . Die gp_printf-Routine verwendet den übergebenen void-Zeiger nicht für irgendeinen Zweck selbst, sondern übergibt ihn an die Zeichenausgabefunktion. Diese Funktion kann dann den Zeiger auf eine FILE * (wenn gp_printf von fprintf aufgerufen wird) oder ein Zeichen ** (wenn es von sprintf aufgerufen wird) oder ein SERIAL_PORT * (wenn es von serial_printf aufgerufen wird) oder was auch immer ausgeben .
Beachten Sie, dass, da jede Art von Information über das void * weitergegeben werden könnte, es keine Beschränkung gg_printf gäbe. Es würde jedoch eine Gefahr geben: Wenn die in der Lücke * übermittelte Information nicht das ist, was die Funktion erwartet, würde Undefiniertes Verhalten (d. H. Potentiell sehr schlimme Dinge) wahrscheinlich resultieren. Es wäre die Verantwortung des Anrufers sicherzustellen, dass der Funktionszeiger und void * richtig gepaart sind; nichts im System würde vor falscher Verwendung schützen.
In .net würde ein Delegat die kombinierte Funktionalität des Funktionszeigers und void * oben bereitstellen, mit dem zusätzlichen Vorteil, dass der Konstruktor des Delegaten sicherstellen würde, dass die Daten vom richtigen Typ für die Funktion waren. Eine praktische Funktion.