Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

[Lösung] Problem mit EventHandler [==> fertige Code-Snippets inkl. Erklärung]

Moderationshinweis von herbivore (07.10.2006 - 10:42:30):

Dies ist ein Thread, auf den aus der FAQ verwiesen wird. Bitte keine weitere Diskussion, sondern nur wichtige Ergänzungen und diese bitte knapp und präzise. Vielen Dank!

progger
myCSharp.de - Member

Avatar #avatar-2094.gif


Dabei seit:
Beiträge: 1334
Herkunft: Nähe von München

Themenstarter:

[Lösung] Problem mit EventHandler [==> fertige Code-Snippets inkl. Erklärung]

beantworten | zitieren | melden

hallo,
ich habe ein problem mit dem erstellen von events (bin auch noch neu in C#). ich habe eine methode, die bei dem event aufgerufen werden soll:

private void Sock_OnJoin(string str_nick,string str_channel)
{
	//....
}
und nun soll das event erstellt werden:

this.Sock.OnJoin+=new System.EventHandler(this.Sock_OnJoin);
aber wie soll ich die parameter übergeben?
ich hab mir als bsp. das click-event bei einem button angeschaut. da hat das event auch 2 parameter, die beim erstellen eines events aber nicht angegeben werden:

this.button1.Click += new System.EventHandler(this.button1_Click);
wenn ich das aber bei mir mach, gibt es eine fehlermeldung:
Zitat
Form1.cs(92): Die Methode 'Form1.Sock_OnJoin(string, string)' stimmt nicht mit dem Delegat 'void System.EventHandler(object, System.EventArgs)' überein.
ich bin über jede hilfe dankbar!
progger
A wise man can learn more from a foolish question than a fool can learn from a wise answer!
Bruce Lee

Populanten von Domizilen mit fragiler, transparenter Außenstruktur sollten sich von der Translation von gegen Deformierung resistenter Materie distanzieren!
Wer im Glashaus sitzt, sollte nicht mit Steinen werfen.
private Nachricht | Beiträge des Benutzers
svenson
myCSharp.de - Member



Dabei seit:
Beiträge: 8775
Herkunft: Berlin

beantworten | zitieren | melden

Deine Methode Sock_OnJoin() hat zwei String-Parameter, der SystemEventHandler-Delegate hat auch zwei, aber andere (Object und EventsArgs). Du verwendest entweder den falschen Delegaten oder die Methode hat die falsche Signatur.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo progger,

die Parameter werden ohnehin nicht beim Registrieren des EventHandlers mitgegeben, sondern erst (und jedesmal neu), wenn die Klasse den Event auslöst.

herbivore
private Nachricht | Beiträge des Benutzers
progger
myCSharp.de - Member

Avatar #avatar-2094.gif


Dabei seit:
Beiträge: 1334
Herkunft: Nähe von München

Themenstarter:

beantworten | zitieren | melden

@svenson: ich kapier nicht so ganz was du meinst. sorry, aner ich bin noch neu in C#.
@herbivore: es gibt aber einen fehler wenn ich es einfach so lass.
danke für eure antworten!
A wise man can learn more from a foolish question than a fool can learn from a wise answer!
Bruce Lee

Populanten von Domizilen mit fragiler, transparenter Außenstruktur sollten sich von der Translation von gegen Deformierung resistenter Materie distanzieren!
Wer im Glashaus sitzt, sollte nicht mit Steinen werfen.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

NICHT empfohlen

beantworten | zitieren | melden

Hallo progger,

machmal sagt Code mehr als tausend Worte. So definiert man einen eigenen Event (folgt nicht den Microsoft-Empfehlungen und ist deshalb nicht empfohlen, dazu später mehr):


using System;

public delegate void MyHandler (string str_nick, string str_channel);

public class Test
{
   public event MyHandler MyEvent;

   protected virtual void OnMyEvent (string str_nick, string str_channel)
   {
      MyHandler myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (str_nick, str_channel);
      }
   }

   public void DoSomething ()
   {
      // ...
      OnMyEvent ("herbivore", "mycsharp");
      // ...
   }

}

abstract class App
{
   public static void Main (string [] astrArg)
   {
      Test test = new Test ();

      test.MyEvent += new MyHandler (ThisMyEvent);
      test.DoSomething ();
   }

   public static void ThisMyEvent (string str_nick, string str_channel)
   {
      Console.WriteLine ("ThisMyEvent (" + str_nick + ", "
                                         + str_channel + ")");
   }
}

herbivore

PS: Dieser Code funktioniert zwar, folgt aber nicht der Empfehlung von MS, an die man sich bei Events wirklich besser halten sollte. Wie ein eigener Event nach der Empfehlung von MS aussehen sollte, steht im folgenden Beitrag.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

[Lösung] sowie weitere Informationen zum Verständnis, zur Definition und Benutzung von Events

beantworten | zitieren | melden

Hallo progger,

und so erstellt man einen eigenen Event von Typ EventHandler (wie es von MS empfohlen wird):


using System;

public class Test
{
   // --- schnipp ---

   public event EventHandler MyEvent;

   protected virtual void OnMyEvent (EventArgs e)
   {
      EventHandler myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (this, e);
      }
   }

   // --- schnapp ---

   public void DoSomething ()
   {
      // ...
      OnMyEvent (EventArgs.Empty);
      // ...
   }
}

static class App
{
   public static void Main (string [] astrArg)
   {
      Test myObject = new Test ();
      myObject.MyEvent += myObject_MyEvent;

      myObject.DoSomething ();
   }

   public static void myObject_MyEvent (Object objSender, EventArgs e)
   {
      Console.WriteLine ("myObject_MyEvent (" + objSender + ", " + e + ")");
   }
}

EDIT (Das Folgende wurde ergänzt, nachdem der Thread aus der FAQ verlinkt wurde):

Am Ende dieses Beitrags werden Implementierungsvarianten vorgestellt, die Features aus höheren Sprachversionen von C# (ab 3.0 bzw. ab 6.0) benutzen und dadurch den Code vereinfachen. Diese Varianten werden bei Nutzung der entsprechenden Sprachversion (und höher) empfohlen.

Eine Implementierungsvariante mit eigener MyEventEventArgs-Klasse findet sich im Beitrag von ThomasR weiter unten oder für (das inzwischen veraltete) C# 1.x im Beitrag von Programmierhans von weiter unten.

Hier noch ein Blick auf die wichtigen Bestandteile des Beispiels:


1. Den Event selbst:

public event EventHandler MyEvent;

Ist in der Klasse definiert, die den Event zur Verfügung stellen will und im Wesentlichen eine Variable, in der die jeweils registrierten EventHandler gespeichert sind. Ist null, wenn kein EventHandler registriert ist. Namen von Events sollten nie mit On beginnen. "MyEvent" ist nur ein Platzhalter, den ihr durch euren Namen für den Event ersetzen solltet, z.B. TextChanged o.ä.


2. Die event-auslösende Methode:

protected virtual void OnMyEvent (EventArgs a)

Deren Namen sollte immer mit On beginnen und danach sollte der Name des Events folgen. Ist natürlich in der gleichen Klasse definiert. Sie wird von dieser Klasse aufgerufen, um den Event zu feuern. Im Beispiel also in DoSomething in der Zeile OnMyEvent (EventArgs.Empty);

Ab C# 3.0 (bis C# 5.x) kann und sollte man die Implementierung der event-auslösenden Methode durch die Raise-Erweiterungsmethode, die weiter unten vorgestellt wird, vereinfachen.

Ab C# 6.0 kann und sollte man die Implementierung der event-auslösenden Methode durch den Null-conditional operator, der weiter unten vorgestellt wird, vereinfachen.


3. Den/die EventHandler:

public static void myObject_MyEvent (Object objSender, EventArgs e)

Der EventHandler ist in der Klasse definiert, die über das Event informiert werden will. Das ist typischerweise eine andere Klasse, als die, die das Event definiert. Namen von EventHandlern sollten nie mit On beginnen. VS benennt EventHandler nach dem auch hier verwendeten Muster: Variablenname Unterstrich EventName.


4a. Das Registrieren des EventHandler für den Event (auch Abonnieren des Events genannt):

EventHandler werden registriert mit +=

myObject.MyEvent += myObject_MyEvent;

Unter .NET 1.x musste man noch schreiben:

myObject.MyEvent += new EventHandler (myObject_MyEvent);

Ab .NET 2.0 ist die kürzere - ansonsten mindestens gleichwertige - Schreibweise möglich, die man unbedingt bevorzugen sollte.


4b. Das Deregistrieren des EventHandler für den Event (auch Entfernen oder Abhängen des EventHandlers genannt):

Zum Deregistrieren eines EventHandlers verwendet man -= statt +=.

Es ist nicht erforderlich, beim Deregistrieren exakt dieselbe Instanz des Delegaten zu verwenden, sondern es reicht, wenn der Delegat auf dieselbe Methode verweist.

myObject.MyEvent += new EventHandler (myObject_MyEvent);
myObject.MyEvent -= new EventHandler (myObject_MyEvent);

Durch die zweite Zeile wird der EventHandler myObject_MyEvent wieder entfernt, obwohl an sich zwei separate Delegaten-Objekte verwendet werden (new), die aber eben beide auf dieselbe Methode verweisen. Schon deshalb ist die C#-2.0-Schreibweise ohne new vorzuziehen:

myObject.MyEvent += myObject_MyEvent;
myObject.MyEvent -= myObject_MyEvent;

Das rechtzeitige Deregistrieren von nicht mehr benötigten Events wird oft vergessen. Das kann allerdings negative Konsequenzen haben. Hat ein Objekt B einen Event von Objekt A registriert und wird das Objekt B nicht mehr benötigt, kann die Speicherfreigabe des Objekts B verzögert oder verhindert werden (memory leak). Der Grund ist, dass das Objekt A noch eine Referenz auf das Objekt B hält. Diese Referenz "versteckt" sich im Delegaten für den EventHandler (Delegate.Target). Deshalb sollte man rechtzeitig alle EventHandler die B registriert hat entfernen.

Noch schlimmer ist es, wenn das Objekt B zerstört wird (Dispose) und dabei die EventHandler nicht entfernt werden. Denn wird ein solches Event ausgelöst, wird der weiterhin registrierte EventHandler aufgerufen und dieser greift dann im schlimmsten Fall auf das bereits zerstörte Objekt zu.

+= und -= sind übrigens die einzigen beiden Operatoren, mit denen man auf Events einer Klasse von außen zugreifen kann. Man kann insbesondere nicht abfragen, ob ein EventHandler bereits registriert ist oder nicht. Das ist aber normalerweise gar nicht nötig. Möchte man einen EventHandler entfernen, obwohl man nicht weiß, ob er momentan registriert ist oder nicht, kann man trotzdem -= benutzen. Im Ergebnis ist der EventHandler deregistriert, egal ob er vorher registriert war oder nicht. Möchte man einen EventHandler hinzufügen, aber nur, wenn er nicht bereits registriert ist, benutzt man erst -= und dann +=. Im Ergebnis ist der EventHandler genau einmal registriert, egal ob er vorher registriert war oder nicht. Das gilt natürlich nur, wenn man konsequent ist und es an keiner Stelle zulässt, dass ein EventHandler mehr als einmal registriert wird.


Überlegungen zur Thread-Sicherheit

In vielen Büchern wird in der event-auslösende Methode direkt das Event auf null abgefragt, bevor es gefeuert wird, also so:


      if (MyEvent != null) {
         MyEvent (this, e);
      }

Auch hier stand bisher der Code so. Dass diese Vorgehensweise nicht thread-sicher ist (zwischen Abfrage und Feuern könnte ein anderer Thread den letzten EventHandler deregistrieren und dann würde das Feuern eine NullReferenceException auslösen), wurde in Kauf genommen, weil viele Klassen sowieso nicht thread-sicher ausgelegt sind und Synchronisierung (z.B. durch lock) nur unnötig Zeit kostet, wenn die Klasse ohnehin nur single-threaded eingesetzt wird.

Es gibt aber eine so preiswerte Lösung, um Thread-Sicherheit zu erreichen, dass man diese grundsätzlich immer einsetzen kann und sollte. Man muss nur dafür sorgen, dass man bei Abfrage und Feuern eine Referenz auf dasselbe Objekt verwendet. Denn Delegaten - und mithin Events - sind immutable. Das Registrieren oder Deregistrieren eines EventHandlers erzeugt immer ein neues Objekt mit den Änderungen. Diesen Umstand kann man nun ausnutzen, denn MyEvent ruft bei jedem Zugriff das jeweils aktuelle Objekt und damit an beiden Stellen ggf. unterschiedliche Objekte ab, wogegen myEvent an beiden Stellen garantiert dasselbe (und da immutable auch unveränderte) Objekt liefert.


      EventHandler myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (this, e);
      }

Diese Lösung kostet nur eine zusätzlich Referenzzuweisung und spart dabei sogar noch einen Property-Zugriff.

Es sei jedoch darauf hingewiesen, dass trotz oder gerade wegen dieses Vorgehen ein EventHandler auch noch aufgerufen werden kann, *nachdem* er schon aus dem Event ausgetragen (deregistriert) wurde, wodurch sich die Notwendigkeit einer zusätzlichen Synchronisierung ergeben kann.

Dank geht an Golo Roden, der mich auf diese Lösung hingewiesen hat.


Vereinfachung der event-auslösenden Methode bei gleicher Thread-Sicherheit (empfohlen)

Für C# 3.0 bis C# 5.x: Mit der Raise-Erweiterungsmethode, die weiter unten vorgestellt wird, lässt sich die Implementierung der event-auslösenden Methode bei gleicher Thread-Sicherheit noch einfacher realisieren. Erweiterungsmethoden gibt es ab C# 3.0, so dass wir das vereinfachte Vorgehen ab C# 3.0 (bis C# 5.x) empfehlen.

Für C# 6.0 und höher: Mit dem Null-conditional operator, der weiter unten vorgestellt wird, lässt sich die Implementierung der event-auslösenden Methode bei gleicher Thread-Sicherheit noch einfacher realisieren. Den Null-conditional operator gibt es ab C# 6.0, so dass wir das vereinfachte Vorgehen ab C# 6.0 empfehlen.


Siehe auch

[FAQ] Event nur bei Benutzeraktion auslösen, nicht bei programmtechnischer Änderung
best practise: Event einer aggregierten Klasse weiterleiten


herbivore

Suchhilfe: 1000 Worte
private Nachricht | Beiträge des Benutzers
progger
myCSharp.de - Member

Avatar #avatar-2094.gif


Dabei seit:
Beiträge: 1334
Herkunft: Nähe von München

Themenstarter:

beantworten | zitieren | melden

Vielen, vielen Dank herbivore. Jetzt hab ichs kapiert. Code sagt wirklich manchmal mehr als 1000 Worte =)
Grüße progger
A wise man can learn more from a foolish question than a fool can learn from a wise answer!
Bruce Lee

Populanten von Domizilen mit fragiler, transparenter Außenstruktur sollten sich von der Translation von gegen Deformierung resistenter Materie distanzieren!
Wer im Glashaus sitzt, sollte nicht mit Steinen werfen.
private Nachricht | Beiträge des Benutzers
Programmierhans
myCSharp.de - Experte

Avatar #avatar-1651.gif


Dabei seit:
Beiträge: 4318
Herkunft: Zentralschweiz

beantworten | zitieren | melden

Zur Ergänzung noch ein wenig kopierter Code wie es aussieht mit eigener EventArgs Klasse

[EDIT](der Code in diesem Beitrag ist für .NET bis Version 1.x; wie man es ab .NET machen sollte, zeigt der folgende Beitrag)[/EDIT]


		public event TextIOEventHandler TextRawReceived;

		protected virtual void OnTextRawReceived(string textReceived)
		{
			TextIOEventHandler textRawReceived = this.TextRawReceived;
			if (textRawReceived!=null)
			{
				textRawReceived(this,new TextIOEventArgs(textReceived));
			}
		}

		public delegate void TextIOEventHandler(object sender, TextIOEventArgs e);

	public class TextIOEventArgs : System.EventArgs
	{
		private readonly string text;
		public TextIOEventArgs(string text) :base()
		{
			this.text=text;
		}
		public string Text
		{get{return this.text;}}
	}


In diesem Beispiel halt nur mit einem Attribut.
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
private Nachricht | Beiträge des Benutzers
ThomasR
myCSharp.de - Member



Dabei seit:
Beiträge: 94

beantworten | zitieren | melden

Hallo,

ich hätte zum Thema eigene EventArgs noch eine Ergänzung. Noch schicker (und von Microsoft ab .NET 2.0 auch empfohlen) ist die Möglichkeit mit Generics. Dadurch erspart man sich die Definition eines eigenen delegates...Ich habe den vorigen Codeausschnitt mal daraufhin angepasst.



public event EventHandler<TextIOEventArgs> TextRawReceived;

        protected virtual void OnTextRawReceived(string textReceived)
        {
            EventHandler<TextIOEventArgs> textRawReceived = this.TextRawReceived;
            if (textRawReceived!=null)
            {
                textRawReceived(this,new TextIOEventArgs(textReceived));
            }
        }


    public class TextIOEventArgs : System.EventArgs
    {
        private readonly string text;
        public TextIOEventArgs(string text) :base()
        {
            this.text=text;
        }
        public string Text
        {get{return this.text;}}
    }

private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

[Lösung] ab C# 3.0: Extensionmethod zum Auslösen von Events

beantworten | zitieren | melden

Hallo zusammen,

weiter oben wird vorgeschlagen, Events wie folgt zu definieren:


   public event EventHandler <EventArgs> MyEvent;

   protected virtual void OnMyEvent (EventArgs e)
   {
      EventHandler <EventArgs> myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (this, e);
      }
   }

Bei Events hat mich schon immer geärgert, dass man diese auf null abfragen muss, bevor man sie feuern kann. Mit folgender Erweiterungsmethode


public static void Raise <T> (this EventHandler <T> eventHandler, Object sender, T e) where T : EventArgs
{
   if (eventHandler != null) {
      eventHandler (sender, e);
   }
}

kann man das Codestück

EventHandler <EventArgs> myEvent = MyEvent;
if (myEvent != null) {
   myEvent (this, e);
}

durch - so ist es ab C# 3.0 (bis C# 5.x) empfohlen -

MyEvent.Raise (this, e);

ersetzen (siehe oben für weitere Informationen zum Verständnis, zur Definition und Benutzung von Events).

Da es sich um eine Erweiterungsmethode handelt, wird diese auch dann aufgerufen, wenn das Event null ist. Es gibt im Gegensatz zu normalen Methoden hier keine NullReferenceException.

Außerdem ist die Variante mit der Erweiterungsmethode auf die gleiche Weise threadsafe wie der bisherige Code. Die Begründung ist die gleiche, nur dass keine zusätzliche lokale Variable benötigt wird, sondern die Variable für den Parameter eventHandler diese Aufgabe übernimmt.

Selbst beim Inlining der Erweiterungsmethode darf der Compiler die Variable für den Parameter nicht wegoptimieren, genausowenig wie der Compiler die lokale Variable im bisherigen Code wegoptimieren darf. Das liegt an dem mit .NET 2.0 eingeführten ".NET Memory Model", nach dem es dem Compiler und dem JIT nicht erlaubt ist, Zugriffe auf eine Variable auf dem Stack durch Zugriffe auf eine Variable auf dem Heap zu ersetzen.

Die Erweiterungsmethode heißt Raise entsprechend der MSDN-Benennungskonvention "Do use the raise terminology for events rather than fire or trigger" (siehe Event Design).

Siehe auch Weitere Informationen zum Verständnis, zur Definition und Benutzung von Events in diesem Thread weiter oben.

herbivore
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

[Lösung] ab C# 6.0: Null-conditional operator beim Auslösen von Events

beantworten | zitieren | melden

Hallo zusammen,

weiter oben wird vorgeschlagen, Events wie folgt zu definieren:


   public event EventHandler <EventArgs> MyEvent;

   protected virtual void OnMyEvent (EventArgs e)
   {
      EventHandler <EventArgs> myEvent = MyEvent;
      if (myEvent != null) {
         myEvent (this, e);
      }
   }

Bei Events hat mich schon immer geärgert, dass man diese auf null abfragen muss, bevor man sie feuern kann. Mit dem mit C# 6.0 eingeführten Null-conditional operator (auch Null-propagation operator genannt) kann man sich diese Abfrage bzw. die im vorigen Beitrag vorgeschlagene Erweiterungsmethode sparen.

Man kann im angegebenen Code das Codestück

EventHandler <EventArgs> myEvent = MyEvent;
if (myEvent != null) {
   myEvent (this, e);
}

einfach durch - so ist es für C# 6.0 und höher empfohlen -

MyEvent?.Invoke (this, e);

ersetzen (siehe oben für weitere Informationen zum Verständnis, zur Definition und Benutzung von Events).

Die Variante mit dem Null-conditional operator ist auf die gleiche Weise threadsafe wie der bisherige Code. Die Begründung ist die gleiche, nur dass keine zusätzliche lokale Variable benötigt wird, sondern eine interne Variable des Null-conditional operator diese Aufgabe übernimmt. Der Null-conditional operator wertet also MyEvent nur einmal aus, obwohl er dies intern einmal auf null abfragt und ggf. einmal Invoke dafür aufruft.

Siehe auch Weitere Informationen zum Verständnis, zur Definition und Benutzung von Events in diesem Thread weiter oben.

herbivore


PS: Invoke ist eine Methode, die jeder Delegat besitzt, um ihn aufzurufen. Zwar kann man einen Delegaten auch über seinen Namen aufrufen,

MyEvent (this, e);

aber den Null-conditional operator darf man bei dieser Aufrufsyntax nicht verwenden. Man darf also nicht schreiben

MyEvent? (this, e);

Da man aber jeden Delegaten auch über Invoke aufrufen kann,

MyEvent.Invoke (this, e);

verwendet man eben diese Aufrufvariante, um sie mit dem Null-conditional operator zu kombinieren. So erklärt sich, wie man zur oben verwendeten Syntax kommt:

MyEvent?.Invoke (this, e);
private Nachricht | Beiträge des Benutzers