Come posso cancellare le iscrizioni agli eventi in C #?


141

Prendi la seguente classe C #:

c1 {
 event EventHandler someEvent;
}

Se ci sono un sacco di abbonamenti a c1's someEventevento e voglio eliminarli tutti, qual è il modo migliore per raggiungere questo obiettivo? Considera anche che gli abbonamenti a questo evento potrebbero essere / sono lambdas / delegati anonimi.

Attualmente la mia soluzione è aggiungere un ResetSubscriptions()metodo a c1quello impostato someEventsu null. Non so se questo abbia conseguenze invisibili.

Risposte:


181

Dall'interno della classe, puoi impostare la variabile (nascosta) su null. Un riferimento null è il modo canonico di rappresentare un elenco di invocazioni vuoto, in modo efficace.

Al di fuori della classe, non puoi farlo - gli eventi sostanzialmente espongono "iscriviti" e "annulla iscrizione" e il gioco è fatto.

Vale la pena essere consapevoli di cosa stanno realmente facendo eventi simili a un campo: stanno creando una variabile e un evento allo stesso tempo. All'interno della classe, si finisce per fare riferimento alla variabile. Dall'esterno, fai riferimento all'evento.

Vedi il mio articolo su eventi e delegati per ulteriori informazioni.


3
Se sei testardo, puoi forzarlo con la riflessione. Vedi stackoverflow.com/questions/91778/… .
Brian,

1
@Brian: dipende dall'implementazione. Se è solo un evento simile a un campo o un EventHandlerList, potresti essere in grado di farlo. Dovresti riconoscere quei due casi però - e potrebbero esserci un numero qualsiasi di altre implementazioni.
Jon Skeet,

@Joshua: No, imposterà la variabile per avere un valore null. Sono d'accordo che la variabile non verrà chiamata hidden.
Jon Skeet,

@JonSkeet Questo è quello che ho (pensato) ho detto. Il modo in cui è stato scritto mi ha confuso per 5 minuti.

@JoshuaLamusga: Beh, hai detto che avrebbe cancellato un elenco di invocazioni, che sembra modificare un oggetto esistente.
Jon Skeet,

34

Aggiungi un metodo a c1 che imposterà 'someEvent' su null.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}

Questo è il comportamento che sto vedendo. Come ho detto nella mia domanda, non so se sto trascurando qualcosa.
programmatore

8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

È meglio utilizzare delegate { }che nullper evitare l'eccezione riferimento null.


2
Perché? Potresti per favore espandere questa risposta?
S. Buda,

1
@ S.Buda Perché se è null allora otterrai un riferimento null. E 'come usare un List.Clear()vs myList = null.
AustinWBryan,

6

L'impostazione dell'evento su null all'interno della classe funziona. Quando si elimina una classe, è sempre necessario impostare l'evento su null, il GC ha problemi con gli eventi e potrebbe non ripulire la classe eliminata se presenta eventi sospesi.


6

La migliore pratica per cancellare tutti gli abbonati è di impostare someEvent su null aggiungendo un altro metodo pubblico se si desidera esporre questa funzionalità all'esterno. Ciò non ha conseguenze invisibili. Il presupposto è ricordare di dichiarare SomeEvent con la parola chiave "evento".

Si prega di consultare il libro - C # 4.0 in breve, pagina 125.

Qualcuno qui ha proposto di utilizzare il Delegate.RemoveAllmetodo. Se lo usi, il codice di esempio potrebbe seguire il modulo seguente. Ma è davvero stupido. Perché non solo SomeEvent=nullall'interno della ClearSubscribers()funzione?

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

5

È possibile ottenere ciò utilizzando i metodi Delegate.Remove o Delegate.RemoveAll.


6
Non credo che funzionerà con espressioni lambda o delegati anonimi.
programmatore

3

Commento concettuale esteso noioso.

Preferisco usare la parola "gestore eventi" anziché "evento" o "delegato". E ha usato la parola "evento" per altre cose. In alcuni linguaggi di programmazione (VB.NET, Object Pascal, Objective-C), "evento" viene chiamato "messaggio" o "segnale" e ha persino una parola chiave "messaggio" e una sintassi specifica dello zucchero.

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;

E, per rispondere a quel "messaggio", un "gestore di eventi" risponde, che sia un singolo delegato o più delegati.

Riepilogo: "Evento" è la "domanda", "i gestori di eventi" sono le risposte.


1

Questa è la mia soluzione:

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

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

È necessario chiamare Dispose()o utilizzare il using(new Foo()){/*...*/}modello per annullare l'iscrizione a tutti i membri dell'elenco di invocazione.


0

Rimuovi tutti gli eventi, supponi che l'evento sia di tipo "Azione":

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}

1
Se sei all'interno del tipo che ha dichiarato l'evento, non devi farlo, puoi semplicemente impostarlo su null, se sei fuori dal tipo, non puoi ottenere l'elenco di invocazioni del delegato. Inoltre, il codice genera un'eccezione se l'evento è nullo, durante la chiamata GetInvocationList.
Servito il

-1

Invece di aggiungere e rimuovere callback manualmente e avere un gruppo di tipi di delegati dichiarati ovunque:

// 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);
    }
}

Puoi provare questo approccio generico:

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();
    }
}

Puoi formattare la tua domanda e rimuovere tutto lo spazio bianco a sinistra? Quando si copia e incolla da un IDE questo può accadere
AustinWBryan

Mi sono appena liberato di quello spazio bianco, mio ​​cattivo
barthdamon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.