Skip to content

8. Benutzerereignisse

Im vorigen Kapitel haben wir das Konzept der Ereignisse im Zusammenhang mit Formularkomponenten behandelt. Nun sehen wir uns an, wie wir Ereignisse in unseren eigenen Klassen erstellen können.

8.1. Vordefinierte Delegate-Objekte

Das Konzept des Objekts delegate wurde bereits im vorigen Kapitel angesprochen, damals jedoch nur kurz gestreift. Als wir uns angesehen haben, wie die Ereignisbehandler der Formularkomponenten deklariert wurden, sind wir auf Code gestoßen, der dem folgenden ähnelt:


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

wobei buttonAfficher eine Komponente vom Typ [Button] war. Diese Klasse verfügt über ein Feld „Click“, das wie folgt definiert ist:

  • [1]: die Klasse [Button]
  • [2]: ihre Ereignisse
  • [3,4]: das Ereignis Click
  • [5]: die Deklaration des Ereignisses [Control.Click] [4].
    • EventHandler ist ein Prototyp (ein Modell) einer Methode, der als Delegat bezeichnet wird.
    • event ist ein Schlüsselwort, das die Funktionalität von delegate und EventHandler einschränkt: Ein Objekt vom Typ delegate verfügt über umfangreichere Funktionen als ein Objekt vom Typ event.

Das delegate EventHandler ist wie folgt definiert:

 

Das Objekt delegate EventHandler bezeichnet ein Methodenmuster:

  • dessen erster Parameter vom Typ „Object“ ist
  • als zweiten Parameter den Typ EventArgs
  • kein Ergebnis zurückgibt

Eine Methode, die dem durch EventHandler definierten Modell entspricht, könnte wie folgt aussehen:


        private void buttonAfficher_Click(object sender, EventArgs e);

Um ein Objekt vom Typ EventHandler zu erstellen, geht man wie folgt vor:

EventHandler evtHandler=new EventHandler(méthode correspondant au prototype  défini par le type EventHandler);

Man kann somit schreiben:

EventHandler evtHandler=new EventHandler(buttonAfficher_Click);

Eine Variable vom Typ delegate ist eigentlich eine Liste von Verweisen auf Methoden, die dem Modell von delegate entsprechen. Um der oben genannten Variablen evtHandler eine neue Methode M hinzuzufügen, verwendet man folgende Syntax:

evtHandler+=new EvtHandler(M);

Die Notation += kann auch verwendet werden, wenn evtHandler eine leere Liste ist.

Die Anweisung:


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

Fügt der Liste der Methoden des Ereignisses buttonAfficher.Click eine Methode vom Typ EventHandler hinzu. Wenn das Ereignis Click für die Komponente buttonAfficher auftritt, führt VB die folgende Anweisung aus:

            buttonAfficher.Click(source, evt);

wobei:

  • source die Komponente vom Typ object ist, die das Ereignis
  • evt vom Typ EventArgs auslöst und keine Informationen enthält

Alle Signaturmethoden vom Typ *void M(object, EventArgs), die dem Ereignis *Click zugeordnet wurden durch:


            this.buttonAfficher.Click += new System.EventHandler(M);

aufgerufen, wobei die von VB übermittelten Parameter (source, evt) verwendet werden.

8.2. Delegate-Objekte definieren

Die Anweisung


    public delegate int Opération(int n1, int n2);

definiert einen Typ namens Opération, der einen Funktionsprototyp darstellt, der zwei Ganzzahlen akzeptiert und eine Ganzzahl zurückgibt. Es ist das Schlüsselwort delegate, das Opération zu einer Funktionsprototypdefinition macht.

Eine Variable op vom Typ Opération dient dazu, eine Liste von Funktionen zu speichern, die dem Prototyp Opération entsprechen:

int f1(int,int)
int f2(int,int)
...
int fn(int,int)

Das Speichern einer Methode fi in der Variablen op erfolgt über op=new Operation(fi) oder einfacher durch op=fi. Um eine Methode fj zur Liste der bereits gespeicherten Funktionen hinzuzufügen, schreibt man op+= fj. Um eine bereits gespeicherte Methode fk zu entfernen, schreibt man op-=fk. Wenn wir in unserem Beispiel n=op(n1,n2) eingeben, werden alle in der Variablen op gespeicherten Methoden mit den Parametern n1 und n2 ausgeführt. Das abgerufene Ergebnis n entspricht dem der zuletzt ausgeführten Methode. Es ist nicht möglich, die von allen Methoden erzeugten Ergebnisse abzurufen. Aus diesem Grund geben Methoden, die in einer delegierten Funktion gespeichert sind, meist ein Ergebnis vom Typ void zurück.

Betrachten wir das folgende Beispiel:


using System;
namespace Chap6 {
    class Class1 {
        // Definition eines Funktionsprototyps
        // nimmt zwei Ganzzahlen als Parameter entgegen und gibt eine Ganzzahl zurück
        public delegate int Opération(int n1, int n2);

        // zwei Instanzmethoden, die dem Prototyp entsprechen
        public int Ajouter(int n1, int n2) {
            Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
            return n1 + n2;
        }//hinzufügen

        public int Soustraire(int n1, int n2) {
            Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
            return n1 - n2;
        }//subtrahieren

        // eine statische Methode, die dem Prototyp entspricht
        public static int Augmenter(int n1, int n2) {
            Console.WriteLine("Augmenter(" + n1 + "," + n2 + ")");
            return n1 + 2 * n2;
        }//erhöhen

        static void Main(string[] args) {

            // Es wird ein Objekt vom Typ „Operation“ definiert, um darin Funktionen zu speichern
            // Die statische Funktion „erhöhen“ wird gespeichert
            Opération op = Augmenter;
            // der Delegat wird ausgeführt
            int n = op(4, 7);
            Console.WriteLine("n=" + n);

            // Erstellung eines Objekts c1 vom Typ class1
            Class1 c1 = new Class1();
            // Die Methode „addieren“ von c1 wird im Delegaten registriert
            op = c1.Ajouter;
            // Ausführung des Delegaten-Objekts
            n = op(2, 3);
            Console.WriteLine("n=" + n);
            // Die Methode „subtrahieren“ von c1 wird im Delegaten gespeichert
            op = c1.Soustraire;
            n = op(2, 3);
            Console.WriteLine("n=" + n);
            //Speicherung von zwei Funktionen im Delegaten
            op = c1.Ajouter;
            op += c1.Soustraire;
            // Ausführung des Delegatenobjekts
            op(0, 0);
            // Eine Funktion wird aus dem Delegaten entfernt
            op -= c1.Soustraire;
            // Der Delegat wird ausgeführt
            op(1, 1);
        }
    }
}
  • Zeile 3: definiert eine Klasse Class1.
  • Zeile 6: Definition von delegate Opération: ein Methodenprototyp, der zwei Parameter vom Typ int akzeptiert und ein Ergebnis vom Typ int zurückgibt
  • Zeilen 9–12: Die Instanzmethode Ajouter hat die Signatur des Delegaten „Opération“.
  • Zeilen 14–17: Die Instanzmethode Soustraire hat die Signatur des Delegaten „Opération“.
  • Zeilen 20–23: Die Klassenmethode Augmenter hat die Signatur des Delegaten „Opération“.
  • Zeile 25: Die Methode Main wird ausgeführt.
  • Zeile 20: Die Variable op ist vom Typ „Delegate Opération“. Sie enthält eine Liste von Methoden mit der Signatur des Typs „Delegate Opération“. Ihr wird eine erste Methodenreferenz zugewiesen, nämlich die auf die statische Methode Class1.Augmenter.
  • Zeile 31: delegate op wird ausgeführt: Es werden alle Methoden ausgeführt, auf die op verweist. Dies geschieht mit den Parametern, die an die Methoden delegate und op. übergeben wurden. Hier wird nur die statische Methode Class1.Augmenter ausgeführt.
  • Zeile 35: Es wird eine Instanz c1 der Klasse Class1 erstellt.
  • Zeile 37: Die Instanzmethode c1.Ajouter wird dem Delegaten zugewiesen. Augmenter war eine statische Methode, Ajouter ist eine Instanzmethode. Damit sollte gezeigt werden, dass dies keine Rolle spielt.
  • Zeile 39: delegate op wird ausgeführt: Die Methode Ajouter wird mit den an delegate op übergebenen Parametern ausgeführt.
  • Zeile 42: Das Gleiche wird mit der Instanzmethode Soustraire wiederholt.
  • Zeilen 46–47: Die Methoden Ajouter und Soustraire werden in die Methoden delegate und op eingefügt.
  • Zeile 49: delegate und op werden ausgeführt: Die beiden Methoden Ajouter und Soustraire werden mit den an delegate und op übergebenen Parametern ausgeführt.
  • Zeile 51: Die Methode Soustraire wird aus delegate und op entfernt.
  • Zeile 53: delegate und op werden ausgeführt: Die verbleibende Methode Ajouter wird nun ausgeführt.

Die Ergebnisse der Ausführung lauten wie folgt:

1
2
3
4
5
6
7
8
9
Augmenter(4,7)
n=18
Ajouter(2,3)
n=5
Soustraire(2,3)
n=-1
Ajouter(0,0)
Soustraire(0,0)
Ajouter(1,1)

8.3. Delegaten oder Schnittstellen?

Die Begriffe „delegates“ und „Schnittstellen“ scheinen sich recht ähnlich zu sein, und man kann sich fragen, worin genau die Unterschiede zwischen diesen beiden Begriffen bestehen. Nehmen wir das folgende Beispiel, das einem bereits untersuchten Beispiel ähnelt:


using System;
namespace Chap6 {
    class Program1 {
        // Definition eines Funktionsprototyps
        // nimmt zwei Ganzzahlen als Parameter entgegen und gibt eine Ganzzahl zurück
        public delegate int Opération(int n1, int n2);

        // zwei Instanzmethoden, die dem Prototyp entsprechen
        public static int Ajouter(int n1, int n2) {
            Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
            return n1 + n2;
        }//hinzufügen

        public static int Soustraire(int n1, int n2) {
            Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
            return n1 - n2;
        }//subtrahieren

        // Ausführung eines Delegaten
        public static int Execute(Opération op, int n1, int n2){
            return op(n1, n2);
        }

        static void Main(string[] args) {
            // Ausführung des Delegaten „Addieren“
            Console.WriteLine(Execute(Ajouter, 2, 3));
            // Ausführung des Delegaten „Subtrahieren“
            Console.WriteLine(Execute(Soustraire, 2, 3));
            // Ausführung eines Multicast-Delegaten
            Opération op = Ajouter;
            op += Soustraire;
            Console.WriteLine(Execute(op, 2, 3));
            // Eine Funktion wird aus dem Delegaten entfernt
            op -= Soustraire;
            // Delegat wird ausgeführt
            Console.WriteLine(Execute(op, 2, 3));
        }
    }
}

In Zeile 20 erwartet die Methode Execute eine Referenz auf ein Objekt vom Typ „Delegate Opération“, das in Zeile 6 definiert ist. Dadurch können verschiedene Methoden (Zeilen 26, 28, 32 und 36) an die Methode Execute übergeben werden. Diese Eigenschaft des Polymorphismus lässt sich auch mit einer Schnittstelle erreichen:


using System;

namespace Chap6 {

    // Schnittstelle IOperation
    public interface IOperation {
        int operation(int n1, int n2);
    }

    // Klasse „Hinzufügen“
    public class Ajouter : IOperation {
        public int operation(int n1, int n2) {
            Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
            return n1 + n2;
        }
    }

    // Klasse „Subtrahieren“
    public class Soustraire : IOperation {
        public int operation(int n1, int n2) {
            Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
            return n1 - n2;
        }
    }

    // Testklasse
    public static class Program2 {
        // Ausführung der einzigen Methode der Schnittstelle IOperation
        public static int Execute(IOperation op, int n1, int n2) {
            return op.operation(n1, n2);
        }

        public static void Main() {
            // Ausführung des Delegaten „Addieren“
            Console.WriteLine(Execute(new Ajouter(), 2, 3));
            // Ausführung des Delegaten „Subtrahieren“
            Console.WriteLine(Execute(new Soustraire(), 2, 3));
        }
    }
}
  • Zeilen 6–8: Die Schnittstelle [IOperation] definiert eine Methode operation.
  • Zeilen 11–16 und 19–24: Die Klassen [Ajouter] und [Soustraire] implementieren die Schnittstelle [IOperation].
  • Zeilen 29–31: Die Methode Execute, deren erster Parameter vom Typ der Schnittstelle IOperation ist. Die Methode Execute erhält nacheinander als ersten Parameter eine Instanz der Klasse Ajouter und anschließend eine Instanz der Klasse Soustraire.

Hier zeigt sich deutlich der polymorphe Charakter, den der Parameter vom Typ delegate im vorherigen Beispiel aufwies. Die beiden Beispiele verdeutlichen gleichzeitig die Unterschiede zwischen diesen beiden Konzepten.

Die Typen delegate und interface sind austauschbar

  • , wenn die Schnittstelle nur eine Methode hat. Der Typ delegate ist nämlich eine Hülle für eine einzige Methode, während die Schnittstelle selbst mehrere Methoden definieren kann.
  • wenn der Multicast-Aspekt von delegate nicht genutzt wird. Dieser Multicast-Begriff existiert nämlich in der Schnittstelle nicht.

Sind diese beiden Bedingungen erfüllt, hat man die Wahl zwischen den beiden folgenden Signaturen für die Methode Execute:


int Execute(IOperation op, int n1, int n2)
int Execute(Opération op, int n1, int n2)

Die zweite Signatur, die delegate verwendet, kann sich als flexibler in der Anwendung erweisen. Denn bei der ersten Signatur muss der erste Parameter der Methode die Schnittstelle IOperation implementieren. Dies erfordert die Erstellung einer Klasse, in der die Methode definiert wird, die als erster Parameter an die Methode Execute übergeben werden soll. Bei der zweiten Signatur ist jede vorhandene Methode mit der richtigen Signatur geeignet. Es sind keine zusätzlichen Konstruktionen erforderlich.

8.4. Ereignisverwaltung

Objekte vom Typ delegate können zur Definition von Ereignissen verwendet werden. Eine Klasse vom Typ C1 kann ein Ereignis vom Typ evt wie folgt definieren:

  • Ein Typ delegate wird innerhalb oder außerhalb der Klasse C1 definiert:
delegate TResult Evt(T1 param1, T2 param2, ...);
  • Die Klasse C1 definiert ein Feld vom Typ delegate Evt:
public Evt Evt1;
  • Wenn eine Instanz c1 der Klasse C1 ein Ereignis melden möchte, führt sie ihren Delegaten Evt1 aus und übergibt ihm die vom Delegaten Evt definierten Parameter. Alle in delegate und Evt1 registrierten Methoden werden dann mit diesen Parametern ausgeführt. Man kann sagen, dass sie über das Ereignis Evt1 benachrichtigt wurden.
  • Wenn ein Objekt c2, das ein Objekt c1 verwendet, über das Auftreten des Ereignisses Evt1 am Objekt c1 benachrichtigt werden soll, wird es eine seiner Methoden c2.M im delegierten Objekt c1.Evt1 des Objekts c1. registrieren. Somit wird seine Methode c2.M jedes Mal ausgeführt, wenn dasEreignis Evt1 auf dem Objekt c1 auftritt. Es kann sich auch abmelden, wenn es nicht mehr über das Ereignis benachrichtigt werden möchte.
  • Da das delegierte Objekt c1.Evt1 mehrere Methoden registrieren kann, können sich verschiedene Objekte ci beim Delegaten c1.Evt1 registrieren, um über das Ereignis Evt1 auf c1 benachrichtigt zu werden.

In diesem Szenario haben wir:

  • eine Klasse, die ein Ereignis auslöst
  • Klassen, die über dieses Ereignis benachrichtigt werden. Man sagt, dass sie das Ereignis abonnieren.
  • einen Typ delegate, der die Signatur der Methoden definiert, die über das Ereignis benachrichtigt werden

Das Framework .NET definiert:

  • eine Standard-Signatur des delegate für ein Ereignis
public delegate void MyEventHandler(object source, EventArgs evtInfo);
  • source: das Objekt, das das Ereignis gemeldet hat
  • evtInfo: ein Objekt vom Typ EventArgs oder ein davon abgeleitetes Objekt, das Informationen zum Ereignis liefert
  • Der Name des delegate muss mit EventHandler enden
  • Eine Standardmethode zur Deklaration eines Ereignisses vom Typ MyEventHandler in einer Klasse:
1
2
3
4
public Class C1{
    public event MyEventHandler Evt1;
...
}

Das Feld Evt1 ist vom Typ delegate. Das Schlüsselwort event dient dazu, die Operationen einzuschränken, die darauf durchgeführt werden können:

  • Von außerhalb der Klasse C1 sind nur die Operationen += und -= möglich. Dies verhindert das Löschen (beispielsweise durch einen Fehler des Entwicklers) der Methoden, die das Ereignis abonniert haben. Man kann das Ereignis einfach abonnieren (+=) oder abbestellen (-=).
  • Nur eine Instanz vom Typ C1 kann den Aufruf Evt1(source,evtInfo) ausführen, der die Ausführung der Methoden auslöst, die das Ereignis Evt1. abonniert haben

Das Framework .NET stellt eine generische Methode bereit, die der Signatur von delegate eines Ereignisses entspricht:

public delegate void EventHandler<TEventArgs>(object source, TEventArgs evtInfo) where TEventArgs : EventArgs
  • delegate EventHandler verwendet den generischen Typ TEventArgs, der dem Typ seines zweiten Parameters entspricht
  • Der Typ TEventArgs muss vom Typ EventsArgs abgeleitet sein (wobei TEventArgs: EventArgs)

Mit diesem generischen Typ delegate folgt die Deklaration eines Ereignisses vom Typ X in der Klasse C dem folgenden empfohlenen Schema:

  • Definieren Sie einen von EventArgs abgeleiteten Typ XEventArgs, um die Informationen zum Ereignis X zu kapseln
  • Definieren Sie in der Klasse C ein Ereignis vom Typ EventHandler<XEventArgs>.
  • In der Klasse C eine geschützte Methode definieren
protected void OnXHandler(XEventArgs e);

, die dazu dient, das Ereignis X an die Abonnenten zu „veröffentlichen“.

Betrachten wir das folgende Beispiel:

  • Eine Klasse Emetteur kapselt eine Temperatur. Diese Temperatur wird überwacht. Wenn diese Temperatur einen bestimmten Schwellenwert überschreitet, muss ein Ereignis ausgelöst werden. Wir nennen dieses Ereignis TemperatureTropHaute. Die Informationen zu diesem Ereignis werden in einem Typ TemperatureTropHauteEventArgs gekapselt.
  • Eine Klasse Souscripteur abonniert das vorgenannte Ereignis. Sobald sie über das Ereignis benachrichtigt wird, zeigt sie eine Meldung auf der Konsole an.
  • Ein Konsolenprogramm erstellt einen Sender und zwei Abonnenten. Es gibt die Temperaturen über die Tastatur ein und speichert sie in einer Instanz vom Typ Emetteur. Ist diese zu hoch, veröffentlicht die Instanz vom Typ Emetteur das Ereignis TemperatureTropHaute.

Um der empfohlenen Vorgehensweise bei der Ereignisverwaltung zu entsprechen, definieren wir zunächst den Typ TemperatureTropHauteEventArgs, um die Informationen zum Ereignis zu kapseln:


using System;

namespace Chap6 {
    public class TemperatureTropHauteEventArgs:EventArgs {
        // Temperatur zum Zeitpunkt des Ereignisses
        public decimal Temperature { get; set; }
        // Hersteller
        public TemperatureTropHauteEventArgs() {
        }
        public TemperatureTropHauteEventArgs(decimal temperature) {
            Temperature = temperature;
        }
    }
}
  • Zeile 6: Die von der Klasse TemperatureTropHauteEventArgs gekapselte Information ist die Temperatur, die das Ereignis TemperatureTropHaute ausgelöst hat.

Die Klasse Emetteur lautet wie folgt:


using System;

namespace Chap6 {
    public class Emetteur {
        static decimal SEUIL = 19;

        // gemessene Temperatur
        private decimal temperature;
        // Name der Quelle
        public string Nom { get; set; }
        // Ereignis gemeldet
        public event EventHandler<TemperatureTropHauteEventArgs> TemperatureTropHaute;

        // Temperatur ablesen/schreiben
        public decimal Temperature {
            get {
                return temperature;
            }
            set {
                temperature = value;
                if (temperature > SEUIL) {
                    // Ereignis wird an die Abonnenten gemeldet
                    OnTemperatureTropHaute(new TemperatureTropHauteEventArgs(temperature));
                }
            }
        }

        // Meldung eines Ereignisses
        protected virtual void OnTemperatureTropHaute(TemperatureTropHauteEventArgs evt) {
            // Übertragung des Ereignisses TemperatureTropHaute an die Abonnenten
            TemperatureTropHaute(this, evt);
        }
    }
}
  • Zeile 5: Der Temperaturschwellenwert, ab dem das Ereignis TemperatureTropHaute veröffentlicht wird.
  • Zeile 10: Der Sender hat einen Namen zur Identifizierung
  • Zeile 12: das Ereignis TemperatureTropHaute.
  • Zeilen 15–26: Die Methode get, die die Temperatur ausgibt, und die Methode set, die sie speichert. Die Methode set sorgt dafür, dass das Ereignis TemperatureTropHaute veröffentlicht wird, wenn die zu speichernde Temperatur den Schwellenwert aus Zeile 5 überschreitet. Sie veranlasst die Veröffentlichung des Ereignisses durch die Methode OnTemperatureTropHauteHandler aus Zeile 29, indem sie ihr als Parameter ein Objekt TemperatureTropHauteEventArgs übergibt, in dem die Temperatur gespeichert ist, die den Schwellenwert überschritten hat.
  • Zeilen 29–32: Das Ereignis TemperatureTropHaute wird veröffentlicht, wobei als erster Parameter der Sender selbst und als zweiter Parameter das als Parameter empfangene Objekt TemperatureTropHauteEventArgs übergeben wird.

Die Klasse Souscripteur, die das Ereignis TemperatureTropHaute abonnieren wird, lautet wie folgt:


using System;

namespace Chap6 {
    public class Souscripteur {
        // Name
        public string Nom { get; set; }

        // Verantwortlicher für das Ereignis TemperatureTropHaute
        public void EvtTemperatureTropHaute(object source, TemperatureTropHauteEventArgs e) {
            // Anzeige auf der Bedienerkonsole
            Console.WriteLine("Souscripteur [{0}] : la source [{1}] a signalé une température trop haute : [{2}]", Nom, ((Emetteur)source).Nom, e.Temperature);
        }
    }
}
  • Zeile 6: Jeder Abonnent wird durch einen Namen identifiziert.
  • Zeilen 9–12: Die Methode, die dem Ereignis TemperatureTropHaute zugeordnet wird. Sie hat die Signatur vom Typ delegate EventHandler<TEventArgs>, die ein Ereignis-Handler aufweisen muss. Die Methode gibt auf der Konsole Folgendes aus: den Namen des Abonnenten, der die Meldung anzeigt, den Namen des Absenders, der das Ereignis gemeldet hat, sowie die Temperatur, die das Ereignis ausgelöst hat.
  • Das Abonnement für das Ereignis TemperatureTropHaute eines Objekts Emetteur erfolgt nicht in der Klasse Souscripteur. Es wird von einer externen Klasse vorgenommen.

Das Programm [Program.cs] verknüpft all diese Elemente miteinander:


using System;
namespace Chap6 {
    class Program {
        static void Main(string[] args) {
            // Erstellung eines Ereignis-Senders
            Emetteur e1 = new Emetteur() { Nom = "e" };
            // Erstellung eines Arrays mit 2 Abonnenten
            Souscripteur[] souscripteurs = new Souscripteur[2];
            for (int i = 0; i < souscripteurs.Length; i++) {
                // Erstellung eines Abonnenten
                souscripteurs[i] = new Souscripteur() { Nom = "s" + i };
                // Abonnieren des Ereignisses TemperatureTropHaute von e1
                e1.TemperatureTropHaute += souscripteurs[i].EvtTemperatureTropHaute;
            }
            // Die Temperaturen werden über die Tastatur eingegeben
            decimal temperature;
            Console.Write("Température (rien pour arrêter) : ");
            string saisie = Console.ReadLine().Trim();
            // solange die eingegebene Zeile nicht leer ist
            while (saisie != "") {
                // Ist die Eingabe eine Dezimalzahl?
                if (decimal.TryParse(saisie, out temperature)) {
                    // Temperatur korrekt – wird gespeichert
                    e1.Temperature = temperature;
                } else {
                    // Fehler wird gemeldet
                    Console.WriteLine("Température incorrecte");
                }
                // Neue Eingabe
                Console.Write("Température (rien pour arrêter) : ");
                saisie = Console.ReadLine().Trim();
            }//while
        }
    }
}
  • Zeile 6: Erstellung des Senders
  • Zeilen 8–14: Erstellung von zwei Abonnenten, die das Ereignis TemperatureTropHaute des Senders abonnieren.
  • Zeilen 20–32: Schleife zur Eingabe der Temperaturen über die Tastatur
  • Zeile 24: Ist die eingegebene Temperatur korrekt, wird sie an das Objekt Emetteur e1 übermittelt, das das Ereignis TemperatureTropHaute auslöst, wenn die Temperatur über 19 °C liegt.

Die Ergebnisse der Ausführung lauten wie folgt:

1
2
3
4
Température (rien pour arrêter) : 17
Température (rien pour arrêter) : 21
Souscripteur [s0] : la source [e] a signalé une température trop haute : [21]
Souscripteur [s1] : la source [e] a signalé une température trop haute : [21]