Delegat (.NET)


Als Delegat bezeichnet man in .NET ein Objekt, welches eine Referenz auf eine statische oder nicht-statische Funktion enthält. Delegaten sind mit den aus C und C++ bekannten Funktionszeigern mit einer Objektreferenz vergleichbar, bieten aber einen größeren Funktionsumfang. Delegaten haben eine Signatur, ähnlich der von Funktionen und Methoden, und alle Funktionen, auf die ein Delegat zeigen soll, müssen einer im Delegat festgelegten Signatur entsprechen. Sie bilden die Grundlage des Event Handling unter .NET.

Inhaltsverzeichnis

Verwendung von Delegaten

Delegaten können für Instanz- und Klassenmethoden erstellt werden. Ein Instanzmethoden-Delegat speichert zusätzlich eine Referenz auf das Objekt, mit dem er erstellt wurde. Im Gegensatz zu Funktionszeigern in C++ kann ein einmal erstellter .NET-Delegat nicht für Methodenaufrufe auf verschiedenen Objekten verwendet werden. Dafür muss man Reflection und MethodInfo-Objekte benutzen. Wegen dieser Objektreferenz hindert ein existierender Delegat das referenzierte Objekt daran, vom Garbage Collector freigegeben zu werden.

Das folgende Codebeispiel veranschaulicht die Verwendung eines Delegaten. Nach dessen Deklaration wurden zwei Instanzen des Delegaten erstellt, mit jeweils einem anderen Ziel. Die Funktion DoSomething bekommt einen Delegaten als Parameter überreicht und kann nun die Funktion ausführen, auf welche der Delegat zeigt, um die Eingabe-Zeichenkette von einer für DoSomething unbekannten Funktion überarbeiten zu lassen. In diesem Beispiel wird DoSomething zweimal aufgerufen und gibt den Text einmal in Groß- und dann in Kleinbuchstaben aus.

Das C#-Beispiel zeigt auch die Verwendung von Delegaten mit Klassen- und Instanzmethoden.

VB:

' Deklaration eines Delegaten "MyStringDelegate", der Referenzen auf Methoden mit Rückgabewert "string"
' und einem string-Parameter aufnehmen kann
Public Delegate Function MyStringDelegate(ByVal input As String) As String
 
 Public Class MainClass
 
   Public Shared Sub Main(ByVal args As String())
     ' Setzen des "Ziels" des dlgtUpper-Delegaten auf Klassenmethode "UpperFunction" 
     Dim dlgtUpper As MyStringDelegate = AddressOf UpperFunction
     ' Setzen des "Ziels" des dlgtLower-Delegaten auf Instanzmethode "LowerFunction"
     Dim dlgtLower As MyStringDelegate = AddressOf LowerFunction
     Dim strHallo As String = "Hallo"
     DoSomething(dlgtUpper, strHallo)
     DoSomething(dlgtLower, strHallo)
   End Sub
 
   Public Shared Sub DoSomething(ByVal dlgt As MyStringDelegate, ByVal input As String)
     Dim output As String = dlgt(input)
     Console.WriteLine(output)
   End Sub
 
   ' Klassenmethode welche vom 1. Delegaten aufgerufen wird.
   Public Shared Function UpperFunction(ByVal inputStr As String) As String
     Return inputStr.ToUpper
   End Function
 
   ' Instanzmethode welche vom 2. Delegaten aufgerufen wird.
   Public Shared Function LowerFunction(ByVal inputStr As String) As String
     Return inputStr.ToLower
   End Function
 End Class

C#:

using System;
 
namespace Wikipedia.DelegateSample
{
   // Deklaration eines Delegaten "MyStringDelegate", der Referenzen auf Methoden mit Rückgabewert "string"
   // und einem string-Parameter aufnehmen kann
   public delegate string MyStringDelegate(string input);
 
   public class MainClass
   {
      public static void Main(string[] args)
      {
         MainClass mc = new MainClass();
 
         // Setzen des "Ziels" des dlgtUpper-Delegaten auf Klassenmethode "UpperFunction" 
         MyStringDelegate dlgtUpper = new MyStringDelegate(MainClass.UpperFunction);
 
         // Setzen des "Ziels" des dlgtLower-Delegaten auf Instanzmethode "LowerFunction"
         MyStringDelegate dlgtLower = new MyStringDelegate(mc.LowerFunction);
 
         DoSomething(dlgtUpper, "Hallo Welt");  // Wird "HALLO WELT" auf der Console ausgeben.
 
         DoSomething(dlgtLower, "Hallo Welt");  // Wird "hallo welt" auf der Console ausgeben.
      }
 
      public static void DoSomething(MyStringDelegate dlgt, string input)
      {
         string output = dlgt(input);
          Console.WriteLine(output);
      }
 
      // Klassenmethode welche vom 1. Delegaten aufgerufen wird.
      public static string UpperFunction(string inputStr)
      {
         return inputStr.ToUpper();
      }
 
      // Instanzmethode welche vom 2. Delegaten aufgerufen wird.
      public string LowerFunction(string inputStr)
      {
         return inputStr.ToLower();
      }
   }
}

Delegaten und Events

Delegaten bilden die Grundlage des Event Handling unter .NET. Das delegate-Schlüsselwort veranlasst den Compiler eine Klasse zu erzeugen, die von System.MulticastDelegate abgeleitet ist. MulticastDelegate ist wiederum von System.Delegate abgeleitet und um die Fähigkeit, eine ganze Aufrufkette zu verwalten erweitert worden. Bei einem Auslösen des MulticastDelegate werden der Reihe nach alle Methoden in der Aufrufkette ausgeführt. Ein Event ist nun ein weiteres Sprachkonzept von .NET, das im Grunde nichts anderes bewirkt als eine Zugangsbeschränkung für den zugrundeliegenden Delegaten zu erreichen. Beispielsweise ist es unmöglich, die gesamte Aufrufkette eines Events zu überschreiben, weil auf einem Event statt dem Zuweisungsoperator nur noch die Operatoren += und -= erlaubt sind, um weitere Methoden an die Aufrufkette anzuhängen. Ein Event benötigt jedoch immer einen zugrundeliegenden Delegaten, der die vom Event übergebenen Parameter näher spezifiziert. Beispielsweise arbeiten die in System.Windows.Forms definierten Klassen zur Oberflächendarstellung intensiv mit dem .NET-Event-System, um u.a. das Neuzeichnen von Elementen zu ermöglichen oder Buttons auf einen Klick des Benutzers reagieren zu lassen.

VB:

 Public Delegate Sub MyEventHandler(ByVal sender As Object, ByVal message As String)
 
 Public Class MyClass
 
   Public Event MyEvent As MyEventHandler
 
   Public Shared Sub Main(ByVal args As String())
     Dim myInstance As MyClass = New MyClass
     Dim dlgt1 As MyEventHandler = AddressOf MyEventSink1
     Dim dlgt2 As MyEventHandler = AddressOf MyEventSink2
     myInstance.MyEvent += dlgt1
     myInstance.MyEvent += dlgt2
     myInstance.FireMyEvent("Hello")
     myInstance.MyEvent -= dlgt1
     myInstance.FireMyEvent("Hello Again")
   End Sub
 
   Public Sub FireMyEvent(ByVal message As String)
     If Not (Me.MyEvent Is Nothing) Then
       Me.MyEvent(Me, message)
     End If
   End Sub
 
   Public Sub MyEventSink1(ByVal sender As Object, ByVal message As String)
     Console.WriteLine("Sink1: " + message)
   End Sub
 
   Public Sub MyEventSink2(ByVal sender As Object, ByVal message As String)
     Console.WriteLine("Sink2: " + message)
   End Sub
 End Class
End Namespace

C#:

namespace Wikipedia.DelegateSample
{
	public delegate void MyEventHandler(object sender, string message);
 
	public class MyClass
	{
		public event MyEventHandler MyEvent;
 
		public static void Main(string[] args)
		{
			MyClass myInstance = new MyClass();
 
			MyEventHandler dlgt1 = new MyEventHandler(myInstance.MyEventSink1);
			MyEventHandler dlgt2 = new MyEventHandler(myInstance.MyEventSink2);
 
			// Eintragen der Delegaten in die Invokation List von MyEvent.
			myInstance.MyEvent += dlgt1;
			myInstance.MyEvent += dlgt2;
 
			// Feuern des Events.
			myInstance.FireMyEvent("Hello");
 
			// Entfernen eines Delegaten von der Invokation List.
			myInstance.MyEvent -= dlgt1;
 
			// Erneutes Feuern des Events.
			myInstance.FireMyEvent("Hello Again");
		}
 
		public void FireMyEvent(string message)
		{
			// Hier wird überprüft ob ein eintrag in der Invokation List vorhanden ist.
			if(this.MyEvent != null)
			{
				// Hier wird jeder Delegat, welcher sich für den Event registriert hat aufgerufen.
				this.MyEvent(this, message);
			}
		}
 
		public void MyEventSink1(object sender, string message)
		{
			Console.WriteLine("Sink1: " + message);
		}
 
		public void MyEventSink2(object sender, string message)
		{
			Console.WriteLine("Sink2: " + message);
		}
	}
}

Delegaten und Threading

Bei Anwendungen mit mehreren Threads finden Delegaten besondere Bedeutung, da sie verwendet werden können, um eine Aufgabe an einen anderen Thread zu delegieren. Beispielsweise ist es in der Windows.Forms-Programmierung erforderlich, so gut wie jede Interaktion mit Oberflächenelementen unter der Kontrolle desjenigen Threads ausführen zu lassen, der diese Elemente auch erzeugt hat (mithin für das GUI verantwortlich ist), weil es andernfalls zu konkurrierenden Zugriffen auf die Oberfläche und damit zu Fehlern kommen kann. Zu diesem Zweck definieren alle Oberflächenelemente eine Methode Invoke(), die einen parameterlosen Delegaten entgegennimmt und ihn im Kontext des GUI-Thread ausführt, sobald dieser mit einer eventuell noch laufenden Aktion fertig wird. Hierdurch findet eine Serialisierung der GUI-Zugriffe statt, der Invoke()-Aufruf unterbricht eine eventuell laufende Operation im GUI-Thread nicht. Das würde der notwendigen Serialisierung von GUI-Zugriffen zuwiderlaufen. Der Hintergrund-Thread, der Invoke() verwendet hat, wartet so lange, bis der übergebene Delegat vollständig ausgeführt wurde. Sofern das nicht notwendig ist, kann alternativ BeginInvoke() zum Aufruf verwendet werden und EndInvoke(), um – falls nötig – auf den Abschluss der delegierten GUI-Operation zu warten.

Gleich aussehend, aber ganz anders implementiert, sind die Methoden BeginInvoke() und EndInvoke(), die ein .NET-Kompiler automatisch für Delegaten generiert. Mittels dieser Methoden kann man die vom Delegaten referenzierte(n) Methode(n) asynchron auf einem eigenen Thread (Begin/EndInvoke()) aufrufen, der nur für die Laufzeit der Delegatenausführung bereitsteht (und aus Performancegründen aus einem Thread-Pool genommen wird).

Weblinks







stock | retire | vm
Why are we here?
All text is available under the terms of the GNU Free Documentation License
This page is cache of Wikipedia. History