Delegaten
Was sind Delegaten?
Delegaten sind Verweistypen, die im Gegensatz zu den übrigen Verweistypen nicht auf
Datenstrukturen, sondern auf Methoden verweisen. Delegaten ermöglichen es unter anderem,
einer aufzurufenden Methode eine weitere Methode als Parameter zu übergeben. Diese
übergebene Methode kann im weiteren Verlauf von der ursprünglich aufgerufenen Methode
ausgeführt werden, ohne dass bekannt sein muss, in welcher Klasse diese Methode enthalten
ist.
Häufig wird dies verwendet, um bei aufwändigen Berechnungen einem überwachenden Objekt
zu signalisieren, dass die Berechnung abgeschlossen wurde. Dafür wird eine Methode des
überwachenden Objektes als Delegat an die berechnende Klasse übergeben. Sobald die
Berechnung beendet ist, wird die Methode als sogenannte Rückrufmethode an dem überwachenden
Objekt aufgerufen, ohne dass die überwachende Klasse der berechnenden Klasse überhaupt
bekannt sein muss.
Sobald einem Delegaten eine Methode zugewiesen wurde, verhält er sich genau wie diese
Methode. Da die Bindung einer Methode an einen Delegaten allerdings nicht feststehend
ist, kann dies dynamisch zur Laufzeit geändert werden, so dass sich das Verhalten der
Anwendung ändern lässt. Die einzige Voraussetzung zur Bindung einer Methode an einen
Delegaten ist, dass beide im Hinblick auf den Typ des Rückgabewertes und der Parameter
übereinstimmen.
Ein Delegat wird ähnlich einer abstrakten Methode definiert, allerdings wird zwischen
dem Zugriffsmodifizierer und dem Rückgabewert zusätzlich das Schlüsselwort
delegate
angegeben. Für die Namensgebung gilt als Richtlinie, dass der Name eines Delegaten um
das Suffix Callback ergänzt wird, für die Schreibweise gilt Pascal Case. Diese Syntax
wird zwar von Microsoft empfohlen, in der Framework Class Library allerdings nicht
konsistent eingehalten, weshalb es einige Delegaten gibt, deren Namen dieser Konvention
nicht folgen.
Im folgenden sollen ergänzend zu der Schnittstelle IPersistable Delegaten eingesetzt
werden, um den Beginn und das Abschließen so wohl des Speicherns wie auch des
Wiederherstellens zu signalisieren. Daher werden zunächst die entsprechenden Delegaten
definiert:
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Executes when storing begins.
/// </summary>
public delegate void StoringCallback();
/// <summary>
/// Executes when storing has finished.
/// </summary>
public delegate void StoredCallback();
/// <summary>
/// Executes when restoring begins.
/// </summary>
public delegate void RestoringCallback();
/// <summary>
/// Executes when restoring has finished.
/// </summary>
public delegate void RestoredCallback();
}
|
Obwohl Delegaten so wohl außerhalb wie auch innerhalb einer Klasse definiert werden
können, ist es üblich, sie außerhalb einer Klasse zu definieren, da sie ansonsten nur
innerhalb der sie umgebenden Klasse verwendbar sind.
Multicast-Delegaten
Nachdem ein Delegat definiert wurde, kann er ebenso wie eine Klasse instanziiert werden.
Während der Delegat als Typ an Hand seiner Signatur nur beschreibt, auf welche Methoden
mit ihm verwiesen werden kann, verweist eine Instanz hingegen auf eine konkrete Methode.
Prinzipiell entspricht diese Unterscheidung zwischen Delegat und Delegatinstanz der
Unterscheidung zwischen Klasse und Objekt.
Eine Delegatinstanz wird ebenso wie ein Feld erzeugt, indem innerhalb einer Klasse ein
entsprechendes Element definiert wird. Um ihr eine Methode zuzuweisen, gibt es zwei
verschiedene Varianten. Zum einen kann direkt die Methode angegeben werden, zum anderen
wird die Methode einem Delegatenkonstruktor übergeben. Da eine Delegatinstanz zunächst
auf genau eine Methode verweist, wird sie häufig auch als Unicast-Delegat bezeichnet.
Im folgenden Code werden vier Delegatinstanzen in der Klasse ComplexNumber definiert,
die auf klasseninterne Methoden verweisen. Da den Delegaten statt dessen auch Methoden
anderer Objekte oder Klassen zugeordnet werden könnten, kann beliebiger Code auf die
Ereignisse des Speicherns und des Wiederherstellens reagieren, ohne dass der Code in
der Klasse ComplexNumber dafür speziell angepasst werden müsste. Delegaten sind dabei
nicht auf objektgebundene Methoden beschränkt, sondern können ebenfalls Verweise auf
klassengebundene Methoden aufnehmen.
Der Aufruf eines Delegaten gleicht dem Aufruf einer Methode. Zudem gelten für einen
objektbezogenen Delegaten die gleichen Richtlinien wie für objektbezogene Methoden,
für einen klassenbezogenen Delegaten gelten die gleichen Richtlinien wie für
klassenbezogene Methoden.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Executes when storing begins.
/// </summary>
public delegate void StoringCallback();
/// <summary>
/// Executes when storing has finished.
/// </summary>
public delegate void StoredCallback();
/// <summary>
/// Executes when restoring begins.
/// </summary>
public delegate void RestoringCallback();
/// <summary>
/// Executes when restoring has finished.
/// </summary>
public delegate void RestoredCallback();
/// <summary>
/// Represents a complex number.
/// </summary>
public sealed class ComplexNumber : IPersistable
{
#region Properties
#endregion
#region Delegates
/// <summary>
/// Executes when storing begins.
/// </summary>
private StoringCallback StoringCallback =
this.Storing;
/// <summary>
/// Executes when storing has finished.
/// </summary>
private StoredCallback StoredCallback =
this.Stored;
/// <summary>
/// Executes when restoring begins.
/// </summary>
private RestoringCallback RestoringCallback =
this.Restoring;
/// <summary>
/// Executes when restoring has finished.
/// </summary>
private RestoredCallback RestoredCallback =
this.Restored;
#endregion
#region Methods
// ...
/// <summary>
/// Stores the current instance in the specified
/// memento.
/// </summary>
/// <param name="memento">The memento.</param>
public void Store(IMemento memento)
{
// Call the storing callback.
this.StoringCallback();
// TODO gr: Store the current instance.
// 2007-06-25
// Call the stored callback.
this.StoredCallback();
}
/// <summary>
/// Restores the current instance from the specified
/// memento.
/// </summary>
/// <param name="memento">The memento.</param>
public void Restore(IMemento memento)
{
// Call the restoring callback.
this.RestoringCallback();
// TODO gr: Restore the current instance.
// 2007-06-25
// Call the restored callback.
this.RestoredCallback();
}
/// <summary>
/// Executes when storing begins.
/// </summary>
public void Storing()
{
// TODO gr: Insert code here.
// 2007-06-26
}
/// <summary>
/// Executes when storing has finished.
/// </summary>
public void Stored()
{
// TODO gr: Insert code here.
// 2007-06-26
}
/// <summary>
/// Executes when restoring begins.
/// </summary>
public void Restoring()
{
// TODO gr: Insert code here.
// 2007-06-26
}
/// <summary>
/// Executes when restoring has finished.
/// </summary>
public void Restored()
{
// TODO gr: Insert code here.
// 2007-06-26
}
#endregion
#region Constructors
#endregion
}
}
|
Allerdings können einer Delegatinstanz problemlos weitere Methoden zugeordnet werden.
Wird ein solcher Delegat aufgerufen, werden nacheinander alle ihm zugeordneten Methoden
aufgerufen. Die Aufrufreihenfolge der einzelnen Methoden ist dabei allerdings unbekannt
ist, weshalb Abhängigkeiten zwischen den Methoden vermieden werden sollten. Solche
Delegatinstanzen werden, da sie auf mehrere Methoden verweisen, als Multicast-Delegaten
bezeichnet. Hingegen werden Delegaten, die auf lediglich eine Methode verweisen, als
Singlecast-Delegaten bezeichnet.
Um einer Delegatinstanz eine weitere, zusätzliche Methode zuzuordnen, wird der Operator
+= verwendet. Dabei kann die gleiche Methode einem Delegaten auch mehrfach zugeordnet
werden, so dass sie mehrfach ausgeführt wird, sobald der Delegat aufgerufen wird. Sofern
als Rückgabewert eines Delegaten nicht
void definiert wird, wird der Rückgabewert der
intern zuletzt aufgerufenen Methode zurückgegeben. Alle anderen Rückgabewerte gehen
verloren.
| C# |
1
2
3
4
5
|
// Assign a method to the delegate.
MyDelegate Foo = this.Bar1;
// Assign an additional method to the delegate.
Foo += this.Bar2;
|
Analog zu += kann die Bindung von Methoden an einen Delegaten mit dem Operator -= wieder
aufgelöst werden, wobei keine Prüfung stattfindet, ob die zu entfernende Methode
tatsächlich an den Delegaten gebunden ist. Wurde eine Methode mehrfach an einen Delegaten
gebunden, so muss jede Bindung einzeln aufgehoben werden. Alternativ kann einem Delegaten
explizit der Wert
null zugewiesen werden, wodurch alle Bindungen an jegliche Methoden
aufgehoben werden.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
|
// Assign a method to the delegate.
MyDelegate Foo = this.Bar1;
// Assign an additional method to the delegate.
Foo += this.Bar2;
// Remove the first method from the delegate.
Foo -= this.Bar1;
// Assign null to the delegate and remove all methods from
// the delegate.
Foo = null;
|
Anonyme Methoden
Unter Umständen kann es aufwändig sein, eine Methode für einen Delegaten zur Verfügung
zu stellen. Dies ist insbesondere dann der Fall, wenn die Methode zum einen nur an den
Delegaten gebunden und ansonsten nirgends verwendet wird, und wenn sie zum anderen nur
sehr wenig Code enthält.
Seit der Version 2.0 von C# gibt es daher die Möglichkeit, Code direkt an einen Delegaten
zu binden, ohne dafür eine eigenständige Methode definieren zu müssen. Ein solches
Konstrukt wird - da der auszuführende Code sich wie eine Methode verhält, allerdings
namenlos ist - als anonyme Methode bezeichnet, wohingegen tatsächliche Methoden als
benannte Methoden bezeichnet werden.
Um einem Delegaten eine anonyme Methode zuzuweisen, wird wiederum das Schlüsselwort
delegate verwendet. Der Methodenrumpf wird wie bei der Definition einer Methode durch
geschweifte Klammern umschlossen, wobei die schließende geschweifte Klammer bei einer
anonymen Methode durch ein zusätzliches Semikolon abgeschlossen werden muss.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
using System;
namespace GoloRoden.GuideToCSharp
{
/// <summary>
/// Executes when storing begins.
/// </summary>
public delegate void StoringCallback();
/// <summary>
/// Executes when storing has finished.
/// </summary>
public delegate void StoredCallback();
/// <summary>
/// Executes when restoring begins.
/// </summary>
public delegate void RestoringCallback();
/// <summary>
/// Executes when restoring has finished.
/// </summary>
public delegate void RestoredCallback();
/// <summary>
/// Represents a complex number.
/// </summary>
public sealed class ComplexNumber : IPersistable
{
#region Properties
#endregion
#region Delegates
/// <summary>
/// Executes when storing begins.
/// </summary>
private StoringCallback StoringCallback = delegate()
{
// TODO gr: Insert code here.
// 2007-06-27
};
/// <summary>
/// Executes when storing has finished.
/// </summary>
private StoredCallback StoredCallback = delegate()
{
// TODO gr: Insert code here.
// 2007-06-27
};
/// <summary>
/// Executes when restoring begins.
/// </summary>
private RestoringCallback RestoringCallback = delegate()
{
// TODO gr: Insert code here.
// 2007-06-27
};
/// <summary>
/// Executes when restoring has finished.
/// </summary>
private RestoredCallback RestoredCallback = delegate()
{
// TODO gr: Insert code here.
// 2007-06-27
};
#endregion
#region Methods
// ...
/// <summary>
/// Stores the current instance in the specified memento.
/// </summary>
/// <param name="memento">The memento.</param>
public void Store(IMemento memento)
{
// Call the storing callback.
this.StoringCallback();
// TODO gr: Store the current instance.
// 2007-06-25
// Call the stored callback.
this.StoredCallback();
}
/// <summary>
/// Restores the current instance from the specified memento.
/// </summary>
/// <param name="memento">The memento.</param>
public void Restore(IMemento memento)
{
// Call the restoring callback.
this.RestoringCallback();
// TODO gr: Restore the current instance.
// 2007-06-25
// Call the restored callback.
this.RestoredCallback();
}
#endregion
#region Constructors
#endregion
}
}
|
Sofern ein Delegat über Parameter verfügt, können diese innerhalb der runden Klammern
wie bei der Definition einer Methode angegeben werden. Insgesamt sollten anonyme Methoden
allerdings sehr sparsam und gezielt eingesetzt werden, da sie dazu verführen,
sämtliche Delegaten vor Ort zu behandeln, statt eine Anwendung sauber zu strukturieren.
Lambdaausdrücke
Seit der Version 3.0 von C# gibt es mit Hilfe der sogenannten Lambdaausdrücke eine noch
weiter verkürzte Möglichkeit, anonyme Methoden zu definieren. Ein Lambdaausdruck kann
überall dort verwendet werden, wo auch eine anonyme Methode möglich wäre. An Stelle des
Schlüsselwortes
delegate wird ein Lambdaausdruck innerhalb
runder Klammern angegeben, die den eigentlichen Ausdruck enthalten.
Ein Lambdaausdruck bildet dabei einen Eingangsparameter auf einen Ausgangsparameter ab,
wobei der Operator => verwendet wird. Um beispielsweise eine komplexe Zahl auf ihren
Absolutbetrag abzubilden, kann der Lambdaausdruck
| C# |
1
|
(c => c.AbsoluteValue)
|
verwendet werden. Der Typ des Ein- und Ausgangsparameters ergibt sich dabei dynamisch,
ebenso spielt die Wahl des Bezeichners zur Identifikation der komplexen Zahl keine Rolle,
er dient nur dazu, die komplexe Zahl überhaupt ansprechen zu können.