ein Projekt von goloroden.de

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.