Insbesondere
in der objektorientierten Welt von .NET hat man oftmals die Anforderung Code
auszuführen, der an einer vollkommen anderen Stelle definiert ist oder dessen
genauen Inhalt man nicht kennt. Ich wurde schon in vielen Kursen von meinen
Teilnehmern gefragt, welche Möglichkeiten uns in diesem Fall zur Verfügung
stehen. In solchen Fällen bietet sich oft die Verwendung von Delegaten an. Ein
Delegat ist an sich nur ein Datentyp, der auf eine Methode zeigt. Der Aufrufer
der Methode wird also zur Laufzeit mit der Zielmethode verbunden. Eine
mitgegebene Liste an Parametern fungiert hierbei als Signatur für die Art von
Methoden, auf die einen Delegaten verweisen darf.
Hier
müssen wir zwischen den zwei Ausprägungen eines Delegaten unterscheiden: Typ
und Instanz.
Ein
Delegat-Typ definiert sozusagen die „Vorlage“, an das sich der Aufrufer und das
Ziel halten müssen. Hier werden Rückgabetyp und die Parameter definiert.
Eine Delegat-Instanz ist ein Objekt, welches sich auf eine oder mehrere
Zielmethoden bezieht. Dieses Objekt entspricht der vorher definiertem
„Vorlage“.
Die
Deklaration eines Delegates beginnt mit dem delegate
Schlüsselwort, gefolgt vom Rückgabetyp, dem Namen des Delegat-Typen und der
Parameterliste.
delegate void MyDelegate(int x);
Um eine
Delegat-Instanz zu erstellen, muss man einer Delegat-Variable eine Methode
zuweisen. Dies kann zum Beispiel folgendermaßen aussehen:
static void Main(string[]
args)
{
MyDelegate d = new MyDelegate(A);
d(12);
}
static void A(int x)
{
// Hier passiert etwas ...
}
Nachdem man
der Delegat-Variable eine Methode zugewiesen hat, kann man mit ihr die
hinterlegte Methode aufrufen.
Wenn man
innerhalb seiner Anwendung einen Delegaten benötigt, der auf eine Methode
zeigt, die einen oder mehrere Parameter hat, jedoch keinen Rückgabetyp
benötigt, kann man entweder seinen eigenen Delegaten schreiben oder den bereits
vordefinierten generischen Action<T>
-
Delegaten aus dem .NET Framework nutzen. Mithilfe des Action<T> - Delegaten können wir Methoden
ausführen, die bis zu 16 Parameter haben.
static void Main(string[]
args)
{
var
act = new Action<int, int>(B);
act(1,2);
}
static void B(int zahl1, int zahl2)
{
Console.WriteLine(zahl1 + zahl2);
}
Im obrigen Codebeispiel wird ein Action<T> - Delegat mit zwei Integer
Parameter erstellt, der auf die Methode B verweist. Beim Aufruf des Delegaten
wird die Methode B ausgeführt und die Summe der übergebenen Parameter in die
Konsole hineingeschrieben.
Für Methoden, die einen Wert
zurückgeben, kann man den generischen Func<T> - Delegaten verwenden. Der Func<T> - Delegat kann, wie der Action<T> - Delegat, bis zu 16 Parameter
nutzen. Der letzte generische Eintrag ist üblicherweise der Rückgabetyp.
static void Main(string[]
args)
{
var
fun = new Func<int, int, string>(C);
Console.WriteLine(fun(5, 3));
}
static string C(int zahl1,int zahl2)
{
return ("Das Ergebnis ist " + (zahl1 + zahl2)).ToString();
}
In diesem Codebeispiel wird ein Func<T> - Delegat erstellt, der zwei
Integer-Zahlen als Parameter verlangt und eine Rückgabe in Form eines Strings
zurückgibt. Der String beinhaltet die Summe der übergebenen Zahlen mit einem
Begleittext. Dieser Text wird nach dem Aufruf der Methode C in die Konsole
hineingeschrieben.
Nun wird man sich sicherlich
fragen, welche der Varianten man nun am besten nutzen sollte.
Prinzipiell könnte man, wenn man
sich ein wenig Schreibarbeit sparen möchte und nicht mehr als 16 Parameter
benötigt, die Action<T>und Func<T> Delegaten ohne Probleme
verwenden. Jedoch sind die Modifizierer out und ref für die Parameter bei Action<T>und Func<T> nicht nutzbar. Für diesen Fall
müsste man sich den Delegaten selbst schreiben.
Ein weiteres sehr nützliches
Feature ist das Verketten von Delegaten. Mithilfe des += Operators kann man mit
einem Delegaten nicht nur auf eine, sondern auf mehrere Zielmethoden
verweisen. Es ist hierbei sogar möglich,
mehrere Delegate-Instanzen miteinander zu verketten. Dies wird im folgenden
Beispiel kurz vorgestellt:
delegate void MyDelegate(int x);
static void Main(string[]
args)
{
MyDelegate del1 = A;
MyDelegate del2 = C;
del1 += B;
del1 += del2;
del1(12);
}
static void A(int x)
{
Console.WriteLine(x);
}
static void B(int x)
{
Console.WriteLine(x * 2);
}
static void C(int x)
{
Console.WriteLine(x * 3);
}
Hier
werden 2 Delegaten erstellt, die je auf die Methode A und C zeigen. Die Methode
B und C werden dann mithilfe des += Operators augenscheinlich an den Delegaten
del1 angehängt. In Wirklichkeit wird im Hintergrund immer eine neue
Delegat-Instanz erstellt, die der Variable del1 zugewiesen wird. Dies liegt an
der Tatsache, dass Delegaten an sich unveränderliche Objekte sind. In C# werden
daher für die Delegaten die += und -= Operationen in die statischen Methoden Combine() und Remove() der Klasse System.Delegate umgewandelt.
Mit dem
Aufruf von del1 werden die Methoden A, B und C der Reihe nach ausgeführt. Die Reihenfolge
hängt in erster Linie davon ab, wann eine Methode „angehängt“ wird.
Delegaten
werden insbesondere im Zusammenhang mit Events sehr häufig verwendet. In einem
künftigen Blogeintrag werde ich dann auf die genauen Zusammenhänge von Delegaten
und Events eingehen.