web-dev-qa-db-fra.com

Créer automatiquement des gestionnaires d'événements C # vides

Il n'est pas possible de déclencher un événement en C # auquel aucun gestionnaire n'est attaché. Ainsi, avant chaque appel, il est nécessaire de vérifier si l'événement est nul.

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

Je voudrais garder mon code aussi propre que possible et me débarrasser de ces contrôles nuls. Je ne pense pas que cela affectera beaucoup les performances, du moins pas dans mon cas.

MyEvent( param1, param2 );

À l'heure actuelle, je résous ce problème en ajoutant manuellement un gestionnaire en ligne vide à chaque événement. Ceci est sujet aux erreurs, car je dois me rappeler de le faire, etc.

void Initialize() {
  MyEvent += new MyEvent( (p1,p2) => { } );
}

Existe-t-il un moyen de générer automatiquement des gestionnaires vides pour tous les événements d'une classe donnée en utilisant la réflexion et un peu de magie CLR?

68
Tomas Andrle

J'ai vu cela sur un autre poste et je l'ai volé sans vergogne et l'ai utilisé dans une grande partie de mon code depuis:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click = delegate {}; // add empty delegate!

//Let you do this:
public void DoSomething() {
    Click(this, "foo");
}

//Instead of this:
public void DoSomething() {
    if (Click != null) // Unnecessary!
        Click(this, "foo");
}

* Si quelqu'un connaît l'origine de cette technique, veuillez la poster dans les commentaires. Je crois vraiment que la source doit être créditée.

( Edit: Je l'ai obtenu de ce post Caractéristiques cachées de C #? )

143
Dinah

La notation:

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

n'est pas thread-safe. Vous devez le faire de cette façon:

EventHandler handler = this.MyEvent;
if ( null != handler ) { handler( param1, param2 ); }

Je comprends que c'est un problème, vous pouvez donc faire une méthode d'assistance:

static void RaiseEvent( EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

puis appelez:

RaiseEvent( MyEvent, param1, param2 );

Si vous utilisez C # 3.0, vous pouvez déclarer la méthode d'assistance comme méthode d'extension:

static void Raise( this EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

puis appelez:

MyEvent.Raise( param1, param2 );

Vous pouvez également créer les méthodes d'extension/d'assistance suivantes pour d'autres gestionnaires d'événements. Par exemple:

static void Raise<TEventArgs>( this EventHandler<TEventArgs> handler,
    object sender, TEventArgs e ) where TEventArgs : EventArgs
{
    if ( null != handler ) { handler( sender, e ); }
}
58
TcKs

En C # 6.0, il n'est pas nécessaire d'aller à aucune de ces longueurs pour effectuer la vérification null, grâce à l'opérateur null conditionnel ?.

Les documents expliquent que l'appel de MyEvent?.Invoke(...) copie l'événement dans une variable temporaire, effectue la vérification nulle et, s'il n'est pas nul, appelle Invoke sur la copie temporaire. Ce n'est pas nécessairement thread-safe dans tous les sens, car quelqu'un aurait pu ajouter un nouvel événement après la copie à la variable temporaire, qui ne serait pas appelée. Cela garantit que vous n'appelerez pas Invoke sur null cependant.

En bref:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click;

public void DoSomething() {
    Click?.Invoke(this, "foo");
}
7
gandaliter

Vous pouvez écrire comme:

MyEvent += delegate { };

Je ne suis pas sûr que ce que vous voulez faire soit correct.

6
leppie

Vous n'avez pas besoin de plusieurs méthodes d'extension pour différents gestionnaires d'événements, vous n'en avez besoin que d'une:

public static class EventHandlerExtensions {
  public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs {
    if (handler != null) handler(sender, args);
  }
}
5
vkelman

C'est une mauvaise idée dans la mesure où le code qui consomme l'événement a désormais une attente que l'objet avec l'événement a été codé par défaut avec une action. Si votre code ne sera jamais utilisé nulle part ailleurs par quelqu'un d'autre, je suppose que vous pouvez vous en tirer.

2
mcintyre321

Les déclarations d'événements C # incluent malheureusement un certain nombre de problèmes de sécurité et d'inefficacités bien connus. J'ai conçu un certain nombre de méthodes d'extension sur les délégués pour les invoquer en toute sécurité, et pour enregistrer/désenregistrer les délégués de manière thread-safe .

Votre ancien code événementiel:

if (someDelegate != null) someDelegate(x, y, z);

Votre nouveau code:

someDelegate.Raise(x, y, z);

Votre ancien code d'inscription à l'événement:

event Action fooEvent;
...
lock (someDummyObject) fooEvent += newHandler;

Votre nouveau code:

Action fooEvent;
...
Events.Add(ref fooEvent, newHandler);

Aucun verrouillage nécessaire, aucun objet factice inséré par le compilateur utilisé pour verrouiller les événements.

1
naasking