Comment puis-je effacer les abonnements aux événements en C #?

141

Prenez la classe C # suivante:

c1 {
 event EventHandler someEvent;
}

S'il y a beaucoup d'abonnements à c1l' someEventévénement de s et que je souhaite tous les effacer, quelle est la meilleure façon d'y parvenir? Considérez également que les abonnements à cet événement peuvent être / sont des lambdas / des délégués anonymes.

Actuellement, ma solution consiste à ajouter une ResetSubscriptions()méthode à c1celle définie someEventsur null. Je ne sais pas si cela a des conséquences invisibles.

programmeur
la source

Réponses:

181

À partir de la classe, vous pouvez définir la variable (masquée) sur null. Une référence nulle est la manière canonique de représenter efficacement une liste d'appels vide.

De l'extérieur de la classe, vous ne pouvez pas faire cela - les événements exposent essentiellement "s'abonner" et "se désabonner" et c'est tout.

Il vaut la peine d'être conscient de ce que font réellement les événements de type champ - ils créent une variable et un événement en même temps. Au sein de la classe, vous finissez par référencer la variable. De l'extérieur, vous faites référence à l'événement.

Voir mon article sur les événements et les délégués pour plus d'informations.

Jon Skeet
la source
3
Si vous êtes têtu, vous pouvez le forcer par réflexion. Voir stackoverflow.com/questions/91778/… .
Brian
1
@Brian: Cela dépend de la mise en œuvre. S'il s'agit simplement d' un événement de type champ ou d'un événement EventHandlerList, vous pourrez peut-être le faire. Vous devrez cependant reconnaître ces deux cas - et il pourrait y avoir un certain nombre d'autres implémentations.
Jon Skeet
@Joshua: Non, cela définira la variable pour qu'elle ait une valeur nulle. Je suis d'accord que la variable ne sera pas appelée hidden.
Jon Skeet
@JonSkeet C'est ce que j'ai (pensé) avoir dit. La façon dont il a été écrit m'a dérouté pendant 5 minutes.
@JoshuaLamusga: Eh bien, vous avez dit que cela effacerait une liste d'appels, ce qui ressemble à la modification d'un objet existant.
Jon Skeet
34

Ajoutez une méthode à c1 qui définira 'someEvent' sur null.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}
programmeur
la source
C'est le comportement que je vois. Comme je l'ai dit dans ma question, je ne sais pas si j'oublie quelque chose.
programmeur
8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

Il vaut mieux utiliser delegate { }que nulld'éviter l'exception de référence nulle.

Feng
la source
2
Pourquoi? Pourriez-vous s'il vous plaît développer cette réponse?
S. Buda
1
@ S.Buda Parce que si c'est nul, vous obtiendrez une réf. Il est comme l' aide d' un List.Clear()contre myList = null.
AustinWBryan
6

Définir l'événement sur null dans la classe fonctionne. Lorsque vous supprimez une classe, vous devez toujours définir l'événement sur null, le GC a des problèmes avec les événements et peut ne pas nettoyer la classe supprimée si elle a des événements en suspens.

Jonathan C Dickinson
la source
6

La meilleure pratique pour effacer tous les abonnés consiste à définir someEvent sur null en ajoutant une autre méthode publique si vous souhaitez exposer cette fonctionnalité à l'extérieur. Cela n'a pas de conséquences invisibles. La condition préalable est de ne pas oublier de déclarer SomeEvent avec le mot-clé 'event'.

Veuillez consulter le livre - C # 4.0 en bref, page 125.

Quelqu'un ici a proposé d'utiliser la Delegate.RemoveAllméthode. Si vous l'utilisez, l'exemple de code peut suivre le formulaire ci-dessous. Mais c'est vraiment stupide. Pourquoi pas juste SomeEvent=nullà l'intérieur de la ClearSubscribers()fonction?

public void ClearSubscribers ()
{
   SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
   // Then you will find SomeEvent is set to null.
}
Cary
la source
5

Vous pouvez y parvenir en utilisant les méthodes Delegate.Remove ou Delegate.RemoveAll.

Michée
la source
6
Je ne pense pas que cela fonctionnera avec des expressions lambda ou des délégués anonymes.
programmeur
3

Commentaire ennuyeux prolongé conceptuel.

J'utilise plutôt le mot "gestionnaire d'événements" au lieu de "événement" ou "délégué". Et utilisé le mot «événement» pour d'autres choses. Dans certains langages de programmation (VB.NET, Object Pascal, Objective-C), "event" est appelé "message" ou "signal", et a même un mot-clé "message" et une syntaxe de sucre spécifique.

const
  WM_Paint = 998;  // <-- "question" can be done by several talkers
  WM_Clear = 546;

type
  MyWindowClass = class(Window)
    procedure NotEventHandlerMethod_1;
    procedure NotEventHandlerMethod_17;

    procedure DoPaintEventHandler; message WM_Paint; // <-- "answer" by this listener
    procedure DoClearEventHandler; message WM_Clear;
  end;

Et, pour répondre à ce "message", un "gestionnaire d'événements" répond, qu'il s'agisse d'un seul délégué ou de plusieurs délégués.

Résumé: "Event" est la "question", "event handler (s)" are the answer (s).

umlcat
la source
1

Voici ma solution:

public class Foo : IDisposable
{
    private event EventHandler _statusChanged;
    public event EventHandler StatusChanged
    {
        add
        {
            _statusChanged += value;
        }
        remove
        {
            _statusChanged -= value;
        }
    }

    public void Dispose()
    {
        _statusChanged = null;
    }
}

Vous devez appeler Dispose()ou utiliser un using(new Foo()){/*...*/}modèle pour désinscrire tous les membres de la liste d'appel.

Jalal
la source
0

Supprimez tous les événements, supposons que l'événement est de type "Action":

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}
Googol
la source
1
Si vous êtes dans le type qui a déclaré l'événement, vous n'avez pas besoin de le faire, vous pouvez simplement le définir sur null, si vous êtes en dehors du type, vous ne pouvez pas obtenir la liste d'appels du délégué. De plus, votre code lève une exception si l'événement est nul, lors de l'appel GetInvocationList.
Servy
-1

Au lieu d'ajouter et de supprimer des rappels manuellement et d'avoir un tas de types de délégués déclarés partout:

// The hard way
public delegate void ObjectCallback(ObjectType broadcaster);

public class Object
{
    public event ObjectCallback m_ObjectCallback;
    
    void SetupListener()
    {
        ObjectCallback callback = null;
        callback = (ObjectType broadcaster) =>
        {
            // one time logic here
            broadcaster.m_ObjectCallback -= callback;
        };
        m_ObjectCallback += callback;

    }
    
    void BroadcastEvent()
    {
        m_ObjectCallback?.Invoke(this);
    }
}

Vous pouvez essayer cette approche générique:

public class Object
{
    public Broadcast<Object> m_EventToBroadcast = new Broadcast<Object>();

    void SetupListener()
    {
        m_EventToBroadcast.SubscribeOnce((ObjectType broadcaster) => {
            // one time logic here
        });
    }

    ~Object()
    {
        m_EventToBroadcast.Dispose();
        m_EventToBroadcast = null;
    }

    void BroadcastEvent()
    {
        m_EventToBroadcast.Broadcast(this);
    }
}


public delegate void ObjectDelegate<T>(T broadcaster);
public class Broadcast<T> : IDisposable
{
    private event ObjectDelegate<T> m_Event;
    private List<ObjectDelegate<T>> m_SingleSubscribers = new List<ObjectDelegate<T>>();

    ~Broadcast()
    {
        Dispose();
    }

    public void Dispose()
    {
        Clear();
        System.GC.SuppressFinalize(this);
    }

    public void Clear()
    {
        m_SingleSubscribers.Clear();
        m_Event = delegate { };
    }

    // add a one shot to this delegate that is removed after first broadcast
    public void SubscribeOnce(ObjectDelegate<T> del)
    {
        m_Event += del;
        m_SingleSubscribers.Add(del);
    }

    // add a recurring delegate that gets called each time
    public void Subscribe(ObjectDelegate<T> del)
    {
        m_Event += del;
    }

    public void Unsubscribe(ObjectDelegate<T> del)
    {
        m_Event -= del;
    }

    public void Broadcast(T broadcaster)
    {
        m_Event?.Invoke(broadcaster);
        for (int i = 0; i < m_SingleSubscribers.Count; ++i)
        {
            Unsubscribe(m_SingleSubscribers[i]);
        }
        m_SingleSubscribers.Clear();
    }
}
Barthdamon
la source
Pouvez-vous formater votre question et supprimer tout l'espace blanc à gauche? Lorsque vous copiez et collez à partir d'un IDE, cela peut arriver
AustinWBryan
Je viens de me débarrasser de cet espace blanc, mon mauvais
barthdamon