Verfasst von: bletra | 17. Oktober 2010

Delegates (Teil 4 von 6): Events

Durch die ersten drei Artikel dieser Reihe, wissen Sie, was Delegate sind, welche generischen Delegate .Net zur Verfügung stellt und was bei Multicast-Delegaten zu beachten ist – alle Delegate sind Multicast-Delegate. In diesem Artikel gehen wir auf eine besonders häufige Form von Multicast-Delegaten ein, den Ereignissen (Events).

Delegate sind in .Net die Basis für Events. Events sind die Technologie, wie verschiedene Komponenten eines Programms und insbesondere einer GUI miteinander kommunizieren. Klickt ein Anwender auf einen Knopf, so ändert sich der Zustand dieses Knopfes (er ist gedrückt) und alle, die sich dafür interessieren werden darüber informiert – alle Zuhörer (Listener) können entsprechend reagieren. Z.b. kann nun ein Datensatz in die Datenbank geschrieben werden. Java bildet dies in entsprechenden Klassen ab. .Net tut dies „versteckt“ mit besonderen Delegaten. Warum ich hier „versteckt“ schreibe hat den Grund, dass normale Delegatvariablen nach außen hin „zu“ sichtbar sind. Dies soll folgender Abschnitt erläutern.

Ereignisse mit „normalen“ Delegatvariablen: Gefährliches Lotto

Betrachten wir zunächst die Möglichkeit, Ereignisse mit Delegatvariablen umzusetzen. .Net bietet uns eine für Ereignisse nützliche Delegatdefinition:

public delegate void EventHandler (Object sender,EventArgs e)

Und als generische Version, mit eigenen Argumenten:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

In einer Klasse soll eine Zustandsänderung (die Ziehung neuer Lottozahlen) per Delegat allen Interessenten mitgeteilt werden, also z.B.

public class Lotto
{
  public EventHandler<LottoEventArgs> LottoAmMittwoch;
  public void Ziehung()
  {
    if (LottoAmMittwoch != null)
      LottoAmMittwoch(this, new LottoEventArgs(new List<int> { 8, 4, 36, 1, 28, 2 }));
  }
}

public class LottoEventArgs: EventArgs
{
  public LottoEventArgs(List<int> ziehung)
  {
    if (ziehung.Count != 6)
      throw new ArgumentException("needs exactly 6 integers", "ziehung");
    this.Ziehung = ziehung;
  }
  public List<int> Ziehung;
}

Ein Kiosk kann zwei Spieler anmelden:

public class Spieler
{
  public string Name;
  public List<int> Tipp;
  public Spieler(string name, List<int> tipp)
  {
    this.Name = name;
    this.Tipp = tipp;
  }
  public void Auswerten(Object sender, LottoEventArgs eargs)
  {
    int countWon = 0;
    for (int i=0;i<Tipp.Count;i++)
      if (eargs.Ziehung.Contains(Tipp[i]))
        countWon++;
    Console.WriteLine(string.Format("{0} hat {1:d} Richtige", Name, countWon));
  }
}
public class Kiosk
{
  Lotto lotto;
  public Kiosk(Lotto lotto)
  {
    this.lotto = lotto;
  }
  public void Anmeldungen()
  {
    Spieler gierigerSpieler = new Spieler("Dagobert", new List<int> { 1, 2, 3, 4, 5, 6 });
    lotto.LottoAmMittwoch += gierigerSpieler.Auswerten;
    Spieler normalerSpieler = new Spieler("Otto", new List<int> { 2, 4, 6, 8, 10, 12 });
    lotto.LottoAmMittwoch += normalerSpieler.Auswerten;
  }
}

Und das Programm nimmt die Anmeldungen entgegen und führt die Ziehung durch:

Lotto lotto = new Lotto();
Kiosk kiosk1 = new Kiosk(lotto);
kiosk1.Anmeldungen();
lotto.Ziehung();

Dies führt zur Ausgabe:

Dagobert hat 3 Richtige
Otto hat 3 Richtige

Bei öffentlichen Delegaten, wie hier verwendet und zur Anmeldung durch den Kiosk benötigt, kann der Kioskbesitzer aber auch mit Dagobert gemeinsame Sache machen und folgende Zeilen einfügen:

//Ziehung manipulieren
lotto.LottoAmMittwoch = gierigerSpieler.Auswerten;
lotto.LottoAmMittwoch(lotto, new LottoEventArgs(gierigerSpieler.Tipp));

Damit wird nur die Auswertung von Dagobert aufgerufen (alle weiteren bis dahin registrierten Methoden werden durch diese eine Methode überschrieben) und LottoAmMittwoch wird mit den Zahlen von Dagobert aufgerufen. Dies führt zur Ausgabe:

Dagobert hat 6 Richtige
Dagobert hat 3 Richtige

Die erste Zeile erfolgt aus der Manipulation, dass LottoAmMittwoch direkt aufgerufen wird und die zweite Zeile resultiert aus dem Aufruf lotto.Ziehung() im eigentlichen Programm.
Wir stehen also vor dem Problem, dass es einerseits von außen möglich sein soll, sich zu registrieren und zu deregistrieren. Auf der anderen Seite soll es aber nicht möglich sein, die Registrierung von anderen zu überschreiben oder das Ereignis selbst auszulösen. Man kann dies selbst programmieren, in dem man Methoden Subscribe und Unsubscribe öffentlich macht und das Delegat selbst privat oder dieses Pattern .Net überlässt und das Schlüsselwort event verwendet. Diese Möglichkeit zeigt der nächste Abschnitt.

Delegatvariablen und das Schlüsselwort event

public class Lotto
{
  public event EventHandler<LottoEventArgs> LottoAmMittwoch;

Damit ist ein Registrieren und Deregistrieren weiter möglich, aber der Betrugsversuch nicht mehr.

Events mit IL-Disassembler

IL-Disassembler zeigt uns, was in beiden Fällen erzeugt wird. Für die Delegatvariable, erhalten wir:

.field public class [mscorlib]System.EventHandler`1<class GenericDelegate.LottoEventArgs> LottoAmMittwoch

Während die Event-Deklaration dies erzeugt:

.event class [mscorlib]System.EventHandler`1<class GenericDelegate.LottoEventArgs> LottoAmMittwoch
{
  .addon instance void GenericDelegate.Lotto::add_LottoAmMittwoch(class [mscorlib]System.EventHandler`1<class GenericDelegate.LottoEventArgs>)
  .removeon instance void GenericDelegate.Lotto::remove_LottoAmMittwoch(class [mscorlib]System.EventHandler`1<class GenericDelegate.LottoEventArgs>)
}

Leider hat hier die GUI von IL-Disassembler schon wieder das Schlüsselwort event reingeschmuggelt, denn begibt man sich auf die Kommandozeile mit:

ildasm "TestDelegate.exe" /out= TestDelegate.il

Dann erhält man:

.field private class [mscorlib]System.EventHandler`1<class GenericDelegate.LottoEventArgs> LottoAmMittwoch
.method public hidebysig specialname instance void
  add_LottoAmMittwoch(class [mscorlib]System.EventHandler`1<class GenericDelegate.LottoEventArgs> 'value') cil managed
  {

Also eigentlich wird auch hier ein field erzeugt, aber erstens ist dies private und nicht public wie oben und zweitens werden öffentliche Methoden zum Registrieren und Deregistrieren erzeugt. Genau das Pattern, was wir am Ende des letzten Abschnitts vorgeschlagen haben 😉

Ein typisches Event-Beispiel

Die bisherigen Ausführungen dieses Artikels dienten dazu, den Unterschied zwischen einer als event deklarierten Variablen und einer gewöhnlichen Delegatvariablen herauszuarbeiten. Abschließend betrachten wir noch ein typisches Beispiel aus der GUI-Programmierung. Wenn Sie beispielsweise einen Button auf die Oberfläche ziehen und diesen Doppelklicken, dann wird sichtbar folgender Code erzeugt:

private void button1_Click(object sender, EventArgs e)
{
  //Ereignisroutine für das Drücken des Buttons
}

Im Hintergrund wird zusätzlich folgender Code erzeugt:

this.button1.Click += new System.EventHandler(this.button1_Click);

was man auch kürzer als

this.button1.Click += this.button1_Click;

schreiben könnte. Die Methode button1_Click wird also registriert. Markiert man Click und drückt F12, erscheint die zugehörige Definition:

public class Control ...
{...
  public event EventHandler Click;
  protected virtual void OnClick(EventArgs e);

Wobei Button von ButtonBase und diese wiederum von Control erbt.
Innerhalb einer Klasse (hier Button bzw. Control) wird also ein event definiert (hier Click) und für dieses Ereignis einer entsprechenden Instanz dieser Klasse (hier button1), kann eine Methode registriert werden (hier button1_Click). Die zugehörige Namenskonvention lässt sich auch sehr schön ableiten.

CustomControl Submit

Im Allgemeinen haben wir also eine Klasse, die ein Event (MyEvent) mit eigenen Argumenten (MyEventArgs – siehe Definition von LottoEventArgs) definiert:

public class MyEventArgs : EventArgs { }

class MyGUIElement : Control
{
  public event EventHandler<MyEventArgs> MyEvent;
  protected virtual void OnMyEvent(MyEventArgs myevent)
  {
    if (MyEvent != null)
    {
      MyEvent(this, myevent);  // Notify Subscribers
    }
  }
  public void PerformMyEvent()
  {
    //possible validation
    OnMyEvent(new MyEventArgs());
  }
}

Wir erzeugen eine Instanz dieser Klasse:

private MyGUIElement myGUIElement;
myGUIElement = new MyGUIElement();

Und registrieren eine entsprechend definierte Methode:

myGUIElement.MyEvent += myGUIElement_MyEvent;

private void myGUIElement_MyEvent(object sender, EventArgs e)
{
  MessageBox.Show("myevent was raised");
}

Jetzt muss dieses Ereignis durch irgendetwas ausgelöst werden, z.B. durch ein Klick, also muss für Click eine Methode registriert werden, die MyEvent auslöst:

myGUIElement.Click += myGUIElement_Click;
private void myGUIElement_Click(object sender, EventArgs e)
{
  //raise submit
  myGUIElement.PerformMyEvent();
}

Anschaulicher wird dies im konkreten Fall eines CustomControls. Der Code basiert auf einem Artikel von arcadia.

public class SubmitButtonControl : System.Windows.Forms.UserControl
{
  private System.Windows.Forms.TextBox txtName;
  private System.Windows.Forms.Button btnSubmit;
  ...
  //use predefined delegate Action
  public event Action Submit;
  protected virtual void OnSubmit()
  {
    if (Submit != null)
    {
      Submit();  // Notify Subscribers
    }
  }

  private void btnSubmit_Click(object sender, System.EventArgs e)
  {
    if (txtName.Text.Length == 0)
    {
      //noch schöner mit ErrorProvider!
      MessageBox.Show("Please enter your name.");
    }
    else
    {
      //submit Ereignis auslösen
      OnSubmit();
    }
  }
  public string UserName
  {
    get { return txtName.Text; }
    set { txtName.Text = value; }
  }
}

Im folgenden Artikel dieser Reihe werden wir asynchrone Delegate betrachten.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

Kategorien

%d Bloggern gefällt das: