Events in .Net

 
03 februari 2008

Iets waar ik me al sinds ik het tegenkwam over verbaasd heb is de standaardmanier waarop in C#/.Net events afgehandeld worden. Dat is nog niet zo lang geleden (zo gaat dat als Junior Programmeur(tm)) maar nu ik inmiddels gedetacheerd ben en op een al lopend project ben ingezet merk ik het nog eens extra. Volgens mij kan het veel beter.

Hoe zat het ook alweer? Als ik een object obj heb dat een event simpleEvent kan afvuren, dan kan ik daarop reageren door een methode met de juiste signatuur aan dat event toe te voegen:

...
obj.simpleEvent += new
   EventHandler(reactToSimpleEvent);
...
public void reactToSimpleEvent (object
    sender, EventArgs e)
{
     // code
}

Als nu het simpleEvent afgevuurd wordt, wordt de code in reactToSimpleEvent uitgevoerd. Hierbij wordt obj dan geacht (maar dit wordt niet afgedwongen) om zichelf als sender mee te geven.

Alle voorgedefinieerde events die ik ben tegengekomen zijn op deze leest geschoeid: de uit te voeren code is steeds een methode met return type void, en argumenten sender van type object en nog een tweede argument, van type EventArgs of een subtype daarvan. In dat tweede argument worden dan de bij het event horende gegevens doorgegeven.

Ik vraag me nu twee dingen af: ten eerste, waarom heeft men EventArgs uitgevonden? MSDN zegt over EventArgs:

This class contains no event data; it is used by events that do not pass state information to an event handler when an event is raised. If the event handler requires state information, the application must derive a class from this class to hold the data.

En inderdaad, als we kijken naar de klassebeschrijving dan zien we dat de klasse alleen een lege constructor definieert, en een static field EventArgs.Empty, met als beschrijving: “Represents an event with no event data.”

Dus… wacht’es even… we geven data mee om aan te geven dat we geen data hebben om mee te geven…?!

Hebben we daar een hele nieuwe klasse voor nodig? What happened to good old null? En trouwens, wat voegt EventArgs toe aan Object als’ie toch geen enkele functionaliteit heeft? Juist het feit dat er zo’n dummy object wordt meegestuurd, betekende dat toen ik de code in dat project wou factoriseren, ik eerst moest nagaan of niemand het event aanriep met een ad hoc subklasse object.

Nou zou je kunnen zeggen dat die mogelijkheid nou juist wel nuttig zou kunnen zijn – maar daar ben ik het niet mee eens: sowieso kan, als je dan toch de functie-argumenten misbruikt, die data net zo goed in het eerste argument (object sender) worden verpakt – dat het ding sender is genoemd en dat je daar als event-gooier alleen maar this in propt is ook maar een conventie. En verder: het feit dat het argument EventArgs als type heeft, geeft – zoals het er nu voorstaat – expliciet aan dat je verwacht dat er geen meegegeven data zal zijn. Als je dat toch doet, dan breekt dat die expliciete verwachting. Ik denk dat het breken van verwachtingen juist een belangrijke oorzaak is van bugs. Als je data wilt meegeven aan je events, dan zal je die ook van de juiste MyEventHandler-declaratie moeten voorzien.

Het tweede wat ik me van .Net-events afvroeg is het volgende. Aangezien je toch onder controle hebt wat de signatuur van het event is, waarom maakt .Net daar dan geen gebruik van? Nu hebben alle eventhandlers precies twee argumenten, waarvan de 2e alle data van het event bevat – en dat betekent dat er voor elk type event een aparte subklasse van zowel EventArgs als van EventHandler is. Als we onze eigen events op dezelfde menier in elkaar zetten, zullen we dus ook steeds per event twee van zulke klassen moeten schrijven. Sinds 2.0 is er een generieke versie EventHandler<TEventArgs> met TEventArgs : EventArgs, maar dat lost dus maar de (kleinste) helft van het probleem op: de argumentklasse TEventArgs moeten we nog steeds zelf maken.

Het enige wat die subklassen van EventArgs doen, is meerdere argumenten voor de eventhandler samenpakken in één dik groot functionaliteitloos object. Waarom niet gewoon een eigen MyEventHandler die al die argumenten gewoon los specificeert…? Dus niet dit:

  // definieer argumentklasse
class MyEventArgs : EventArgs 
{
  public string beschrijving;
  public int ID;
  public DateTime tijdstip;
  public Point plaats;
    // ...en nog andere data...
}


class MyEventThrower 
{
  public delegate void MyEventHandler(
      object sender, MyEventArgs args
  );

  public event MyEventHandler myEvent;
  
  ...
  if (myEvent != null) 
  {    
      // pak argumenten in
      MyEventArgs args = new MyEventargs(
          beschr, id, tijd, pl, enz);

      // gooi event
      myEvent(this, args);
  }
  ...
}

Maar gewoon direct zo:

  // geen argumentklasse nodig

class MyEventThrower 
{

  public delegate void MyEventHandler (
      object sender,
      string beschrijving, int ID,
      DateTime tijdstip, Point plaats
        // ...andere data ook 'inline'...
  );

  public event MyEventHandler MyEvent;

  ...
  if (MyEvent != null) 
  {
        // gooi event direct
      MyEvent(this, besc, id, tijd, pl, enz);
  }
  ...
}

De afhandeling van events zonder data komt er dan gewoon uit te zien als public delegate void EventHandler(object sender)zonder nutteloze EventArgs. Door het op deze manier te doen scheelt het per eventtype tenminste een klasse om te schrijven, en je hoeft niet, elke keer voordat je een event wilt gooien, je data eerst in te pakken in zo’n zelfontworpen MyEventArgs-object.

Zelfs in de 14e eeuw wist de monnik Willem van Ockham het al: entia non sunt multiplicanda praeter necessitatem, oftewel entities should not be multiplied beyond necessity. Laten we dat in de 21e eeuw dus vooral ook niet doen.


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development

Tags: , , ,


Reacties (5)

  • Jasper Stein schreef:

    (@Jan-Maarten: Ik had je een reactie beloofd – het heeft even geduurd, maar hier is’ie dan)

    Het standaardiseren van het event-mechanisme is op zich een redelijk argument. Inderdaad kun je, als er geen ‘state’-parameter (waarom noemen ze dat eigenlijk zo?) in je event signature zit, later niet beslissen om toch iets extras mee te geven.

    In de discussie die je noemt wordt gesproken over een extra member ExtendedInfo die in .Net 1.0 beta 1 er kennelijk nog in zat. Maar nu die eruit is gehaald is het argument om een EventArgs-klasse te hebben volledig verdwenen, en ik vraag me dus af waarom het nog in het framework zit. Op dit moment is er geen enkel verschil meer tussen EventArgs.Empty en new Object();

    Dat Microsoft hun eigen events op deze manier insteekt, kan ik met het (tot nog toe hypothetische) argument van Stefan begrijpen, ivm. backwards compatibility. Dat argument geldt overigens alleen als je aan de EventArgs-klasse members wilt toevoegen die event-specifiek zijn, en dus niet in Object thuishoren.

    Waarom er dan ook nog wordt aangeraden dat je je eigen events ook daarmee opzet vraag ik me af; je eigen programmacode heb je in principe onder controle, dus waarom zou je niet het event type zo specifiek mogelijk aangeven? Zolang je niet een softwarebibliotheek ontwikkelt (waar anderen dan weer tegenaan programmeren) kan je je EventArgs domweg weglaten: er zijn (in mijn wellicht beperkte ervaring) maar weinig projecten die zo groot en zo zwaar event-gedreven zijn dat het echt een probleem is om de clients aan die extra parameter te laten wennen, mocht dat uberhaupt al eens nodig zijn.

    Ik vind het argument van ‘later kan je het uitbreiden’ dus niet zo sterk. Maar misschien zijn er mensen met een andere mening?

    Geplaatst op 13 september 2008 om 10:55 Permalink

  • Een van de voordelen (volgens mij) van de EventArgs constructie is dat je op deze manier generieker bezig kan zijn. De EventHandlers kunnen allemaal zo dezelfde handtekening hebben en hierdoor kan je meerdere Events laten worden afgehandeld door dezelfde EventHandler. Dus de constructie levert een flexibelere manier op om Events aan EventHandlers te koppelen. Om deze flexibiliteit te verkrijgen heb je consistentie nodig en dat kost een prijs: EventArgs.Empty.

    Een ander voordeel is dat je met EventArgs het systeem makkelijker kan uitbreiden. Als je later een extra parameter wilt meegeven, dan is dat gewoon mogelijk.

    Het meeste van deze info heb ik ook weer ergens anders gevonden (maar ik kan het er mee eens zijn), dus is het misschien wel interessant om eens te kijken op http://forums.devx.com/showthread.php?t=54803
    waar eigenlijk Jasper zijn vraag ook aan de orde komt, en dat al 7 jaar geleden…

    Geplaatst op 05 mei 2008 om 15:13 Permalink

  • Jasper Stein schreef:

    Het heeft even geduurd, maar toch een reactie terug :-)

    Ruben, wat jij zegt is inderdaad een mogelijkheid. Je zou zelfs kunnen denken aan verschillende extensies,
    class MyEventArgs1<T> : Eventargs
    class MyEventArgs2<T,S> : Eventargs
    class MyEventArgs3<T,S,U> : Eventargs
    class MyEventArgs4<T,S,U,V> : Eventargs
    enz.
    Alleen een MyEventArgs0 is dan hetzelfde als gewoon EventArgs, en dat is nu juist wat ik wil afschaffen.

    Stefan, wat de originele bedoeling was van EventArgs weet ik niet; MSDN zegt er niet zo veel over. In elk geval is die toekomst met 3.5 nog steeds niet ingetreden. Ik ben benieuwd wat voor nuttige functionaliteit je uberhaupt aan de EventArgs-klasse zou kunnen hangen, en ook waarom er zo sterk wordt aanbevolen om juist wel EventArgs te gebruiken terwijl daar zo te zien weinig voordeel aan zit en wel nadelen. Heeft dit te maken met ballast uit het verleden oid.? Is er iemand die dit weet?

    Geplaatst op 27 april 2008 om 20:34 Permalink

  • Stefan Bookholt schreef:

    Als ik het me goed herinner, dan is de eventArgs klasse gebouwd met het oog op de toekomst. In een latere versie van het framework kan het zijn dat deze klasse wel betekenisvolle waarden krijgt. Verder ben je niet verplicht deze te gebruiken maar het wordt sterk aanbevolen. Bijvoorbeeld: een public delegate void MijnEventhandler(object sender, DateTime tijd) is ook valide.

    Geplaatst op 02 april 2008 om 14:06 Permalink

  • Ruben Rorije schreef:

    Hoi Jasper,

    Je hebt een goed punt, een empty event args zou wat mij betreft ook uberhaupt niet in de parameter lijst voor moeten komen.

    Het aanmaken van een klasse voor elk event kan volgens mij wel redelijk worden ingeperkt door een generieke klasse.

    public class MyEventArgs : EventArgs
    {
    public T Value {get;set;}
    public MyEventArgs(T value) { Value = value;}
    }

    Nadeel is dat dit alleen werkt als je een parameter hebt.

    Geplaatst op 30 maart 2008 om 9:41 Permalink