2009-08-07 4 views
30

Je suis sûr que je suis tout simplement pas comprendre quelque chose fondamentale sur les événements et/ou des délégués en C#, mais pourquoi je ne peux pas faire les tests booléennes dans cet exemple de code:En C#, pourquoi ne puis-je pas tester si un gestionnaire d'événement est nul en dehors de la classe qu'il a définie?

public class UseSomeEventBase { 
    public delegate void SomeEventHandler(object sender, EventArgs e); 
    public event SomeEventHandler SomeEvent; 
    protected void OnSomeEvent(EventArgs e) { 
     // CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS. 
     if (SomeEvent != null) SomeEvent(this, e); 
    } 
} 

public class UseSomeEvent : UseSomeEventBase { 
    public bool IsSomeEventHandlerNull() { 
     // "LEFT HAND SIDE" COMPILER ERROR 
     return SomeEvent == null; 
    } 
} 

class Program { 
    static void Main(string[] args) { 
     var useSomeEvent = new UseSomeEvent(); 
     useSomeEvent.SomeEvent +=new UseSomeEventBase.SomeEventHandler(FuncToHandle); 
     // "LEFT HAND SIDE" COMPILER ERROR 
     if (useSomeEvent.SomeEvent == null) { 

     } 
     var useSomeEventBase = new UseSomeEventBase(); 
     useSomeEventBase.SomeEvent += new UseSomeEventBase.SomeEventHandler(FuncToHandle); 
     // "LEFT HAND SIDE" COMPILER ERROR 
     if (useSomeEventBase.SomeEvent == null) { 

     } 
    } 

    static void FuncToHandle(object sender, EventArgs e) { } 
} 

Répondre

55

Un événement est vraiment juste une opération "add" et opération "remove". Vous ne pouvez pas obtenir la valeur, vous ne pouvez pas définir la valeur, vous ne pouvez pas l'appeler - vous pouvez simplement souscrire un gestionnaire pour l'événement (add) ou en désinscrire un (remove). C'est bien - c'est l'encapsulation, simple et claire. Il appartient à l'éditeur de mettre en œuvre l'ajout/la suppression de façon appropriée, mais à moins que l'éditeur ne choisisse de rendre les détails disponibles, les abonnés ne peuvent pas modifier ou accéder aux parties spécifiques à l'implémentation.

événements comme le terrain en C# (où vous ne spécifiez pas l'ajout/suppression de bits) cachent cela - ils créent une variable d'un type délégué et un événement. Les implémentations d'ajout/suppression de l'événement utilisent simplement la variable pour garder une trace des abonnés.

À l'intérieur de la classe, vous faites référence à la variable (vous pouvez donc obtenir les délégués actuellement souscrits, les exécuter, etc.) et en dehors de la classe vous faites référence à l'événement lui-même.

L'alternative aux événements de type champ est l'endroit où vous implémentez explicitement l'ajout/suppression vous-même, par ex.

private EventHandler clickHandler; // Normal private field 

public event EventHandler Click 
{ 
    add 
    { 
     Console.WriteLine("New subscriber"); 
     clickHandler += value; 
    } 
    remove 
    { 
     Console.WriteLine("Lost a subscriber"); 
     clickHandler -= value; 
    } 
} 

Voir my article on events pour plus d'informations.

Bien sûr, l'éditeur d'événements peut également plus d'informations disponibles - vous pouvez écrire une propriété comme ClickHandlers pour retourner le délégué multi-distribution actuelle ou HasClickHandlers retourner s'il y en a ou non. Cela ne fait pas partie du modèle d'événement principal.

+0

+1 pour la réponse. Juste pour y ajouter, vous pouvez, bien sûr, implémenter les opérations add/remove en tant que méthodes explicites avec un délégué multi-cast en interne. Dans ce cas, vous pouvez suivre vous-même les abonnés et les non abonnés et ainsi donner accès à des informations supplémentaires (par exemple, le nombre d'abonnés, etc.) - si vous le souhaitez. – LBushkin

+0

Yup - va ajouter cela, merci. –

+0

Il peut être utile de noter que les événements * raison * sont implémentés de cette façon afin que toute classe ne puisse pas changer le comportement de la classe avec l'événement simplement en accédant à l'événement (comme une propriété delegate) et en l'effaçant liste des abonnés. – womp

0

Vous devriez faire cela à partir de la classe de base. C'est la raison exacte pour laquelle vous l'avez fait:

protected void OnSomeEvent(EventArgs e) { 
    // CANONICAL WAY TO TEST EVENT. OF COURSE, THIS WORKS. 
    if (SomeEvent != null) SomeEvent(this, e); 
} 

Vous ne pouvez pas accéder aux événements d'une classe dérivée. En outre, vous devriez rendre cette méthode virtuelle, afin qu'elle puisse être surchargée dans une classe dérivée.

+2

Soyez conscient que ce n'est pas threadsafe. Il peut lancer une exception NullReferenceException si le dernier gestionnaire est supprimé après le test mais avant l'appel. –

+0

@Jon - j'ai lu votre article à ce sujet, mais je ne pense pas que cela se produirait dans cette application web. – gabe

2

est ici une question légèrement différente

Quelle valeur est là pour tester un événement externe défini pour null?

En tant que consommateur externe d'un événement, vous ne pouvez faire 2 opérations

  • Ajouter un gestionnaire
  • Supprimer un gestionnaire

Le nul ou non l'état NULL de l'événement a pas d'incidence sur ces 2 actions. Pourquoi voulez-vous exécuter un test qui ne fournit aucune valeur perceptible?

+0

J'essaie d'écrire une classe wrapper wrapper pour 2 contrôles Web tiers mal conçus que je suis tenu d'utiliser pour un projet (je déteste ces contrôles fous). J'essaie d'imposer l'utilisation d'une méthode spécifique en tant que gestionnaire d'événement pour un événement dans un objet membre (référence à l'un des contrôles Web) d'une classe (le wrapper). Je veux faire une vérification dans la classe wrapper pour m'assurer que l'utilisateur de la classe wrapper a ajouté un gestionnaire d'événement spécifique à un événement de l'objet membre. c'est très spécifique à ces 2 contrôles stupides et difficile à expliquer ... – gabe

1

C'est une règle en place lors de l'utilisation du mot clé 'event'.Lorsque vous créez un événement, vous limitez l'interaction de classe en dehors du délégué à une relation "subscribe/unsubscribe", ce qui inclut les cas d'héritage. Rappelez-vous un événement est essentiellement une propriété, mais pour les appels de méthode, il est pas vraiment un objet lui-même, si vraiment il ressemble plus à ceci:

public event SomeEventHandler SomeEvent 
{ 
    add 
    { 
      //Add method call to delegate 
    } 
    remove 
    { 
      //Remove method call to delegate 
    } 
} 
14

Vous pouvez facilement utiliser une approche très simple ici pour vous abonner pas à plusieurs reprises à un événement.

Chacune des 2 approches ci-dessous peuvent être utilisés:

  1. approche Drapeau: _getWarehouseForVendorCompletedSubscribed est une variable privée initialisée à false.

    if (!_getWarehouseForVendorCompletedSubscribed) 
        { 
         _serviceClient.GetWarehouseForVendorCompleted += new EventHandler<GetWarehouseForVendorCompletedEventArgs>(_serviceClient_GetWarehouseForVendorCompleted); 
         _getWarehouseForVendorCompletedSubscribed = true; 
        } 
    
  2. Se désabonner approche: Inclure un désabonnement chaque fois que vous souhaitez vous abonner.

    _serviceClient.GetWarehouseForVendorCompleted -= new 
        EventHandler<GetWarehouseForVendorCompletedEventArgs> 
    (_serviceClient_GetWarehouseForVendorCompleted); 
    
    
    _serviceClient.GetWarehouseForVendorCompleted += new 
         EventHandler<GetWarehouseForVendorCompletedEventArgs> 
         (_serviceClient_GetWarehouseForVendorCompleted); 
    
+0

+1 C'est BRILLANT et c'est exactement ce que je cherchais.Je ne comprends pas pourquoi cela n'a pas obtenu plus de votes –

+0

Pour l'approche de désinscription, comment pouvez-vous 'supprimer' un EventHandler s'il n'est pas déjà ajouté? – noahnu

+3

S'il n'a pas été ajouté, il ignore simplement le - = –

3

Voici la réponse:

using System; 
delegate void MyEventHandler(); 
class MyEvent 
{ 
    string s; 
    public event MyEventHandler SomeEvent; 
    // This is called to raise the event. 
    public void OnSomeEvent() 
    { 
     if (SomeEvent != null) 
     { 
      SomeEvent(); 
     } 

    } 

    public string IsNull 
    { 
     get 
     { 
      if (SomeEvent != null) 
       return s = "The EventHandlerList is not NULL"; 
      else return s = "The EventHandlerList is NULL"; ; 
     } 
    } 
} 

class EventDemo 
{ 
    // An event handler. 
    static void Handler() 
    { 
     Console.WriteLine("Event occurred"); 
    } 

    static void Main() 
    { 
     MyEvent evt = new MyEvent(); 
     // Add Handler() to the event list. 
     evt.SomeEvent += Handler; 
     // Raise the event. 
     //evt.OnSomeEvent(); 
     evt.SomeEvent -= Handler; 
     Console.WriteLine(evt.IsNull); 
     Console.ReadKey(); 
    } 
} 
0

Éditeur de l'événement surcharge implicitement que += et -= opérations, et d'autres opérations ne sont pas mis en œuvre dans l'éditeur à cause des raisons évidentes, comme expliqué ci-dessus , tels que ne veulent pas donner le contrôle à l'abonné pour changer les événements.

Si nous voulons valider si un événement particulier est abonné dans la classe d'abonné, un meilleur éditeur définira un indicateur dans sa classe lorsque l'événement est abonné et effacera l'indicateur lorsqu'il est désabonné.

Si l'abonné peut accéder au drapeau de l'éditeur, très facilement identifiable si l'événement particulier est abonné ou non en vérifiant la valeur de l'indicateur.