2011-04-11 5 views
11

Je suis assez convaincu que ce n'est pas possible, mais je vais quand même demander.Abonnement à un seul événement

Afin de faire un abonnement unique coup à des événements, je me retrouve souvent en utilisant ce modèle (inventé l'auto-):

EventHandler handler=null; 
handler = (sender, e) => 
{ 
    SomeEvent -= handler; 
    Initialize(); 
}; 
SomeEvent += handler; 

Il est tout à fait beaucoup de passe-partout, et il fait également ReSharper whinge sur les fermetures modifiées. Y at-il un moyen de transformer ce modèle en une méthode d'extension ou similaire? Une meilleure façon de le faire?

Idéalement, je voudrais quelque chose comme:

SomeEvent.OneShot(handler) 
+0

Je ne vois aucune raison pour laquelle vous ne pouvez pas mettre le code standard dans une méthode d'extension. Ou n'ai-je pas eu la question? – Achim

+0

Comment passez-vous l'événement pour la désinscription? A-t-il un type de base commun? Un délégué n'est-il pas un type de valeur (c'est-à-dire si vous transmettez l'événement d'une manière ou d'une autre) – spender

+0

@spender: Tous les délégués sont des types de référence. –

Répondre

4

Ce n'est pas très facile à factoriser à une méthode d'extension, car la seule façon vous pouvez vous référer à un événement en C# est en vous inscrivant (+=) à ou désabonnement (-=) de celui-ci (à moins qu'il ne soit déclaré dans la classe en cours).

Vous pouvez utiliser la même approche que dans les extensions réactives: Observable.FromEvent prend deux délégués pour s'abonner à l'événement et se désabonner. Donc, vous pouvez faire quelque chose comme ça:

public static class EventHelper 
{ 
    public static void SubscribeOneShot(
     Action<EventHandler> subscribe, 
     Action<EventHandler> unsubscribe, 
     EventHandler handler) 
    { 
     EventHandler actualHandler = null; 
     actualHandler = (sender, e) => 
     { 
      unsubscribe(actualHandler); 
      handler(sender, e); 
     }; 
     subscribe(actualHandler); 
    } 
} 

... 

Foo f = new Foo(); 
EventHelper.SubscribeOneShot(
    handler => f.Bar += handler, 
    handler => f.Bar -= handler, 
    (sender, e) => { /* whatever */ }); 
+0

Bien essayé, mais avec toute la délégation d'abonnement/désabonnement c'est à peu près la même quantité de passe-partout, mais en utilisant une interface inconnue qui n'est pas évidente en première lecture.Il n'y a pas non plus d'application de souscription/désabonnement dans les délégués fournis par l'utilisateur, ce qui signifie qu'il serait facile de rompre et me laisse préoccupé par le nom de la méthode. Merci, mais je vais coller avec le mien fttb :) – spender

+0

Je dirais que c'est un pas dans la bonne direction. Après tout, l'intention du code est clairement indiquée par le nom de la méthode et il y a un peu moins de code à taper. C'est certainement un début. –

+1

+1, vous ne pouvez quand même pas retirer 'f.Bar + =/- = handler' de l'appelant, et l'abonnement/suppression effectué dans la méthode d'extension au lieu du gestionnaire peut être considéré comme une fonctionnalité, pas comme un bogue: –

1

Le code suivant fonctionne pour moi. Ce n'est pas parfait d'avoir à spécifier l'événement via une chaîne, mais je n'ai pas de colle pour résoudre ce problème. Je suppose que ce n'est pas possible dans la version C# actuelle.

using System; 
using System.Reflection; 

namespace TestProject 
{ 
    public delegate void MyEventHandler(object sender, EventArgs e); 

    public class MyClass 
    { 
     public event MyEventHandler MyEvent; 

     public void TriggerMyEvent() 
     { 
      if (MyEvent != null) 
      { 
       MyEvent(null, null); 
      } 
      else 
      { 
       Console.WriteLine("No event handler registered."); 
      } 
     } 
    } 

    public static class MyExt 
    { 
     public static void OneShot<TA>(this TA instance, string eventName, MyEventHandler handler) 
     { 
      EventInfo i = typeof (TA).GetEvent(eventName); 
      MyEventHandler newHandler = null; 
      newHandler = (sender, e) => 
          { 
           handler(sender, e); 
           i.RemoveEventHandler(instance, newHandler); 
          }; 
      i.AddEventHandler(instance, newHandler); 
     } 
    } 

    public class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass c = new MyClass(); 
      c.OneShot("MyEvent",(sender,e) => Console.WriteLine("Handler executed.")); 
      c.TriggerMyEvent(); 
      c.TriggerMyEvent(); 
     } 
    } 
} 
+0

Juste découvert, qu'un accesseur d'événement personnalisé peut également être une option. Mais cela dépend de vos préférences et exigences. – Achim

1

Je suggère d'utiliser un événement « personnalisé » de sorte que vous avez accès à la liste d'appel, puis déclencher l'événement en utilisant Interlocked.Exchange à lire en même temps et effacer la liste d'appel. Si vous le souhaitez, l'abonnement/désabonnement/levée d'événement peut être effectué de manière thread-safe en utilisant une simple pile de listes chaînées; Lorsque l'événement est déclenché, le code peut, après Interlocked.Exchange, inverser l'ordre des éléments de la pile. Pour la méthode de désinscription, je suggérerais probablement simplement de placer un drapeau dans l'élément de liste d'invocation. Cela pourrait théoriquement provoquer une fuite de mémoire si les événements étaient subséquemment souscrits et désabonnés sans que l'événement ne soit jamais déclenché, mais cela rendrait la méthode de désinscription thread-safe très simple. Si l'on voulait éviter une fuite de mémoire, on pourrait tenir compte du nombre d'événements non inscrits qui sont encore dans la liste; Si un trop grand nombre d'événements non inscrits sont dans la liste lors d'une tentative d'ajout d'un nouveau, la méthode add pourrait parcourir la liste et les supprimer. Toujours réalisable en code thread-safe entièrement sans verrou, mais plus compliqué.

Questions connexes