Freigeben über


System.Delegate und das delegate-Schlüsselwort

Vorhergehend

In diesem Artikel werden die Klassen in .NET behandelt, die Stellvertretungen unterstützen und wie diese dem delegate Schlüsselwort zugeordnet werden.

Was sind Stellvertretungen?

Stellen Sie sich eine Stellvertretung als Möglichkeit vor, einen Verweis auf eine Methode zu speichern, ähnlich wie Sie einen Verweis auf ein Objekt speichern können. Genau wie Sie Objekte an Methoden übergeben können, können Sie Methodenverweise mithilfe von Delegaten übergeben. Dies ist nützlich, wenn Sie flexiblen Code schreiben möchten, in dem verschiedene Methoden "angeschlossen" sein können, um unterschiedliche Verhaltensweisen bereitzustellen.

Angenommen, Sie haben einen Rechner, der Vorgänge für zwei Zahlen ausführen kann. Anstatt Addition, Subtraktion, Multiplikation und Division hartcodiert in separate Methoden zu implementieren, können Sie Delegaten verwenden, um beliebige Operationen darzustellen, die zwei Zahlen akzeptieren und ein Ergebnis zurückgeben.

Delegattypen definieren

Sehen wir uns nun an, wie Stellvertretungstypen mithilfe des delegate Schlüsselworts erstellt werden. Wenn Sie einen Delegattyp definieren, erstellen Sie im Wesentlichen eine Vorlage, die beschreibt, welche Art von Methoden in diesem Delegat gespeichert werden kann.

Sie definieren einen Delegattyp mithilfe einer Syntax, die ähnlich wie eine Methodensignatur aussieht, aber mit dem delegate Schlüsselwort am Anfang:

// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);

Dieser Calculator Delegat kann Verweise auf jede Methode enthalten, die zwei int Parameter akzeptiert und einen int zurückgibt.

Sehen wir uns ein praktisches Beispiel an. Wenn Sie eine Liste sortieren möchten, müssen Sie dem Sortieralgorithmus mitteilen, wie Elemente verglichen werden. Sehen wir uns an, wie Delegaten bei der List.Sort()-Methode helfen. Der erste Schritt besteht darin, einen Delegattyp für den Vergleichsvorgang zu erstellen:

// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);

Dieser Comparison<T> Delegat kann Verweise auf eine beliebige Methode enthalten, die:

  • Verwendet zwei Parameter vom Typ T
  • Gibt eine int (in der Regel -1, 0 oder 1 zurück, um "kleiner als", "gleich" oder "größer als") anzugeben.

Wenn Sie einen Delegatentyp wie diesen definieren, generiert der Compiler automatisch eine Klasse, die von System.Delegate abgeleitet ist und Ihre Signatur erfüllt. Diese Klasse behandelt die gesamte Komplexität des Speicherns und Aufrufens der Methodenverweise für Sie.

Der Delegattyp Comparison ist ein generischer Typ, was bedeutet, dass er mit jedem beliebigen Typ Tarbeiten kann. Weitere Informationen zu Generika finden Sie unter Generische Klassen und Methoden.

Beachten Sie, dass die Syntax zwar ähnlich wie beim Deklarieren einer Variablen aussieht, aber sie deklarieren tatsächlich einen neuen Typ. Sie können Delegatentypen innerhalb von Klassen, direkt innerhalb von Namespaces oder sogar im globalen Namespace definieren.

Hinweis

Das Deklarieren von Delegattypen (oder anderen Typen) direkt im globalen Namespace wird nicht empfohlen.

Der Compiler generiert außerdem Add- und Remove-Handler für diesen neuen Typ, sodass Clients dieser Klasse Methoden aus der Aufrufliste einer Instanz hinzufügen und entfernen können. Der Compiler erzwingt, dass die Signatur der hinzugefügten oder entfernten Methode mit der Signatur übereinstimmt, die beim Deklarieren des Delegattyps verwendet wird.

Deklarieren von Instanzen von Delegaten

Nach dem Definieren des Delegattyps können Sie Instanzen (Variablen) dieses Typs erstellen. Stellen Sie sich dies als Erstellung eines "Slot" vor, in dem Sie einen Verweis auf eine Methode speichern können.

Wie alle Variablen in C# können Sie Stellvertretungsinstanzen nicht direkt in einem Namespace oder im globalen Namespace deklarieren.

// Inside a class definition:
public Comparison<T> comparator;

Der Typ dieser Variablen ist Comparison<T> (der Stellvertretungstyp, den Sie zuvor definiert haben), und der Name der Variablen lautet comparator. comparator verweist an diesem Punkt noch nicht auf eine Methode – es ist wie ein leerer Steckplatz, der darauf wartet, befüllt zu werden.

Sie können Stellvertretungsvariablen auch als lokale Variablen oder Methodenparameter deklarieren, genau wie jeder andere Variablentyp.

Delegaten aufrufen

Sobald Sie über eine Delegatinstanz verfügen, die auf eine Methode verweist, können Sie diese Methode über den Delegat aufrufen. Sie rufen die Methoden in der Aufrufliste eines Delegaten auf, indem Sie den Delegaten so aufrufen, als wäre er eine Methode.

Hier erfahren Sie, wie die Sort() Methode den Vergleichsdelegat verwendet, um die Reihenfolge der Objekte zu bestimmen:

int result = comparator(left, right);

In dieser Zeile wird die Methode, die dem Delegaten zugeordnet ist, vom Code aufgerufen. Sie behandeln die Delegatvariable so, als ob es sich um einen Methodennamen handelt, und rufen sie mithilfe der normalen Methodenaufrufssyntax auf.

Diese Codezeile führt jedoch zu einer gefährlichen Annahme: Sie geht davon aus, dass dem Delegaten eine Zielmethode hinzugefügt wurde. Wenn keine Methoden angefügt wurden, würde die obige Zeile eine NullReferenceException werfen. Die Muster, die zur Behebung dieses Problems verwendet werden, sind komplexer als eine einfache NULL-Prüfung und werden später in dieser Reihe behandelt.

Zuweisen, Hinzufügen und Entfernen von Aufrufzielen

Jetzt wissen Sie, wie Sie Stellvertretungstypen definieren, Delegateninstanzen deklarieren und Stellvertretungen aufrufen. Aber wie verbindet man tatsächlich eine Methode mit einem Delegaten? Hier kommt die Zuweisung von Delegationen ins Spiel.

Um einen Delegat zu verwenden, müssen Sie ihm eine Methode zuweisen. Die Methode, die Sie zuweisen, muss dieselbe Signatur aufweisen (das heißt, gleiche Parameter und Rückgabetyp), wie sie der Delegattyp definiert.

Sehen wir uns ein praktisches Beispiel an. Angenommen, Sie möchten eine Liste von Zeichenfolgen nach deren Länge sortieren. Sie müssen eine Vergleichsmethode erstellen, die der Stellvertretungssignatur Comparison<string> entspricht:

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

Diese Methode akzeptiert zwei Zeichenfolgen und gibt eine ganze Zahl zurück, die angibt, welche Zeichenfolge "größer" ist (in diesem Fall länger). Die Methode wird als privat deklariert, was perfekt in Ordnung ist. Sie müssen die Methode nicht Teil Ihrer öffentlichen Schnittstelle machen, um sie mit einem Delegaten zu verwenden.

Jetzt können Sie diese Methode an die List.Sort() Methode übergeben:

phrases.Sort(CompareLength);

Beachten Sie, dass Sie den Methodennamen ohne Klammern verwenden. Dadurch wird der Compiler aufgefordert, den Methodenverweis in einen Delegaten zu konvertieren, der später aufgerufen werden kann. Die Sort() Methode ruft ihre CompareLength Methode immer dann auf, wenn sie zwei Zeichenfolgen vergleichen muss.

Sie können auch expliziter sein, indem Sie eine Delegatvariable deklarieren und die Methode ihm zuweisen:

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

Beide Ansätze erreichen dasselbe. Der erste Ansatz ist präziser, während die zweite die Stellvertretungszuweisung expliziter macht.

Bei einfachen Methoden ist es üblich, Lambda-Ausdrücke zu verwenden, anstatt eine separate Methode zu definieren:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

Lambda-Ausdrücke bieten eine kompakte Möglichkeit zum Definieren einfacher Methoden inline. Die Verwendung von Lambda-Ausdrücken für Stellvertretungsziele wird in einem späteren Abschnitt ausführlicher behandelt.

Bisher zeigen die Beispiele Delegaten mit einer einzelnen Zielmethode. Delegatobjekte können jedoch Aufruflisten unterstützen, die mehrere Zielmethoden an ein einzelnes Delegatenobjekt angefügt haben. Diese Funktion ist besonders nützlich für Ereignisbehandlungsszenarien.

Delegatklassen und MulticastDelegate-Klassen

Im Hintergrund basieren die Stellvertretungsfeatures, die Sie verwendet haben, auf zwei Schlüsselklassen im .NET Framework: Delegate und MulticastDelegate. Sie arbeiten in der Regel nicht direkt mit diesen Klassen, aber sie bieten die Grundlage, die Delegaten funktionieren lässt.

Die System.Delegate Klasse und ihre direkte Unterklasse System.MulticastDelegate bieten die Frameworkunterstützung für das Erstellen von Stellvertretungen, das Registrieren von Methoden als Stellvertretungsziele und das Aufrufen aller Methoden, die bei einem Delegaten registriert sind.

Hier ist ein interessantes Designdetail: System.Delegate und System.MulticastDelegate sind nicht selbst Delegattypen, die man verwenden kann. Stattdessen dienen sie als Basisklassen für alle spezifischen Delegattypen, die Sie erstellen. Die C#-Sprache verhindert, dass Sie direkt von diesen Klassen erben . Sie müssen stattdessen das delegate Schlüsselwort verwenden.

Wenn Sie das delegate-Schlüsselwort verwenden, um einen Delegattyp zu deklarieren, erstellt der C#-Compiler automatisch eine Klasse mit Ihrer spezifischen Signatur, die von MulticastDelegate abgeleitet ist.

Warum dieses Design?

Dieses Design hat seine Wurzeln in der ersten Version von C# und .NET. Das Designteam hatte mehrere Ziele:

  1. Typsicherheit: Das Team wollte sicherstellen, dass die Typsicherheit beim Verwenden von Delegaten erzwungen wird. Dies bedeutet, dass Delegaten mit dem richtigen Typ und der richtigen Anzahl von Argumenten aufgerufen werden und dass Rückgabetypen zur Kompilierzeit ordnungsgemäß überprüft werden.

  2. Leistung: Wenn der Compiler konkrete Delegatenklassen generiert, die bestimmte Methodensignaturen darstellen, kann die Laufzeit Stellvertretungsaufrufe optimieren.

  3. Einfachheit: Delegaten wurden in der .NET-Version 1.0 enthalten, die vor der Einführung von Generics war. Der Entwurf musste innerhalb der zeitlichen Beschränkungen arbeiten.

Die Lösung bestand darin, dass der Compiler die konkreten Delegatklassen erstellt hat, die Ihren Methodensignaturen entsprechen, um die Typsicherheit zu gewährleisten und gleichzeitig die Komplexität von Ihnen zu verbergen.

Arbeiten mit Delegate-Methoden

Obwohl Sie abgeleitete Klassen nicht direkt erstellen können, verwenden Sie gelegentlich Methoden, die für die Klassen Delegate und MulticastDelegate definiert sind. Hier sind die wichtigsten, die Sie kennen müssen:

Jeder Delegat, mit dem Sie arbeiten, wird von MulticastDelegate abgeleitet. Ein „Multicastdelegat” bedeutet, dass mehr als ein Methodenziel beim Aufrufen über einen Delegaten aufgerufen werden kann. Das ursprüngliche Design berücksichtigte eine Unterscheidung zwischen Delegaten, die nur eine Methode im Vergleich zu Stellvertretungen aufrufen konnten, die mehrere Methoden aufrufen könnten. In der Praxis erwies sich diese Unterscheidung als weniger nützlich als ursprünglich gedacht, sodass alle Delegates in .NET mehrere Zielmethoden unterstützen können.

Die am häufigsten verwendeten Methoden beim Arbeiten mit Stellvertretungen sind:

  • Invoke(): Ruft alle Methoden auf, die der Stellvertretung zugeordnet sind.
  • BeginInvoke() / EndInvoke(): Wird für asynchrone Aufrufmuster verwendet (wird jedoch async/await jetzt bevorzugt)

In den meisten Fällen rufen Sie diese Methoden nicht direkt auf. Stattdessen verwenden Sie die Methodenaufrufssyntax für die Delegatvariable, wie in den obigen Beispielen gezeigt. Wie Sie später in dieser Reihe sehen werden, gibt es jedoch Muster, die direkt mit diesen Methoden funktionieren.

Zusammenfassung

Nachdem Sie nun gesehen haben, wie die C#-Sprachsyntax den zugrunde liegenden .NET-Klassen zugeordnet ist, sind Sie bereit, zu erforschen, wie stark typisierte Delegaten in komplexeren Szenarien verwendet, erstellt und aufgerufen werden.

Nächster