¿Cómo puedo borrar las suscripciones a eventos en C #?

141

Tome la siguiente clase de C #:

c1 {
 event EventHandler someEvent;
}

Si hay una gran cantidad de suscripciones a c1's someEventevento y quiero aclarar a todos, ¿cuál es la mejor manera de lograr esto? También tenga en cuenta que las suscripciones a este evento podrían ser / son lambdas / delegados anónimos.

Actualmente mi solución es agregar un ResetSubscriptions()método a c1ese conjunto someEventcomo nulo. No sé si esto tiene consecuencias invisibles.

programador
fuente

Respuestas:

181

Desde dentro de la clase, puede establecer la variable (oculta) en nulo. Una referencia nula es la forma canónica de representar una lista de invocación vacía, efectivamente.

Desde fuera de la clase, no puede hacer esto: los eventos básicamente exponen "suscribirse" y "darse de baja" y eso es todo.

Vale la pena ser consciente de lo que realmente están haciendo los eventos de campo: están creando una variable y un evento al mismo tiempo. Dentro de la clase, terminas haciendo referencia a la variable. Desde afuera, hace referencia al evento.

Vea mi artículo sobre eventos y delegados para más información.

Jon Skeet
fuente
3
Si eres terco, puedes forzarlo a través de la reflexión. Ver stackoverflow.com/questions/91778/… .
Brian
1
@Brian: Depende de la implementación. Si es solo un evento similar a un campo o un evento EventHandlerList, es posible que pueda hacerlo. Sin embargo, tendría que reconocer esos dos casos, y podría haber muchas otras implementaciones.
Jon Skeet
@ Joshua: No, configurará la variable para que tenga un valor nulo. Estoy de acuerdo en que no se llamará a la variable hidden.
Jon Skeet
@ JonSkeet Eso es lo que (pensé) dije. La forma en que fue escrita me confundió durante 5 minutos.
@JoshuaLamusga: Bueno, dijiste que borraría una lista de invocación, que suena como modificar un objeto existente.
Jon Skeet
34

Agregue un método a c1 que establecerá 'someEvent' en nulo.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}
programador
fuente
Ese es el comportamiento que estoy viendo. Como dije en mi pregunta, no sé si estoy pasando por alto algo.
programador
8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

Es mejor usar delegate { }que nullevitar la excepción de referencia nula.

Feng
fuente
2
¿Por qué? ¿Podría por favor ampliar esta respuesta?
S. Buda
1
@ S.Buda Porque si es nulo, obtendrás una referencia nula. Es como usar una List.Clear()vs myList = null.
AustinWBryan
6

Establecer el evento como nulo dentro de la clase funciona. Cuando desecha una clase, siempre debe establecer el evento como nulo, el GC tiene problemas con los eventos y puede no limpiar la clase eliminada si tiene eventos colgantes.

Jonathan C Dickinson
fuente
6

La mejor práctica para borrar todos los suscriptores es establecer someEvent en nulo agregando otro método público si desea exponer esta funcionalidad al exterior. Esto no tiene consecuencias invisibles. La condición previa es recordar declarar SomeEvent con la palabra clave 'event'.

Consulte el libro - C # 4.0 en pocas palabras, página 125.

Alguien aquí propuso usar el Delegate.RemoveAllmétodo. Si lo usa, el código de muestra podría seguir el siguiente formulario. Pero es realmente estúpido. ¿Por qué no solo SomeEvent=nulldentro de la ClearSubscribers()función?

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

Puede lograr esto utilizando los métodos Delegate.Remove o Delegate.RemoveAll.

Micah
fuente
66
No creo que esto funcione con expresiones lambda o delegados anónimos.
programador
3

Comentario aburrido extendido conceptual.

Prefiero usar la palabra "controlador de eventos" en lugar de "evento" o "delegado". Y usé la palabra "evento" para otras cosas. En algunos lenguajes de programación (VB.NET, Object Pascal, Objective-C), "evento" se denomina "mensaje" o "señal", e incluso tiene una palabra clave "mensaje" y una sintaxis específica de azúcar.

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;

Y, para responder a ese "mensaje", un "controlador de eventos" responde, ya sea un delegado único o múltiples delegados.

Resumen: "Evento" es la "pregunta", "controlador de eventos" son las respuestas.

umlcat
fuente
1

Esta es mi solución:

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

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

Debe llamar Dispose()o usar un using(new Foo()){/*...*/}patrón para cancelar la suscripción de todos los miembros de la lista de invocación.

Jalal
fuente
0

Elimine todos los eventos, suponga que el evento es del tipo "Acción":

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}
Googol
fuente
1
Si está dentro del tipo que declaró el evento, no necesita hacer esto, simplemente puede configurarlo como nulo, si está fuera del tipo, entonces no puede obtener la lista de invocación del delegado. Además, su código arroja una excepción si el evento es nulo, al llamar GetInvocationList.
Servy
-1

En lugar de agregar y eliminar devoluciones de llamada manualmente y tener un montón de tipos de delegado declarados en todas partes:

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

Podrías probar este enfoque genérico:

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
fuente
¿Puede formatear su pregunta y eliminar todo el espacio en blanco a la izquierda? Cuando copie y pegue desde un IDE, esto puede suceder
AustinWBryan
Acabo de deshacerme de ese espacio en blanco, mi mal
barthdamon