Como posso limpar inscrições de eventos em c #?

141

Tome a seguinte classe C #:

c1 {
 event EventHandler someEvent;
}

Se houver muitas inscrições para c1o someEventevento e eu quiser limpá-las, qual é a melhor maneira de conseguir isso? Considere também que as assinaturas deste evento podem ser / são delegadas lambdas / anônimas.

Atualmente, minha solução é adicionar um ResetSubscriptions()método c1que define someEventcomo nulo. Não sei se isso tem consequências invisíveis.

programador
fonte

Respostas:

181

De dentro da classe, você pode definir a variável (oculta) como nula. Uma referência nula é a maneira canônica de representar uma lista de chamadas vazia, efetivamente.

Fora da turma, você não pode fazer isso - os eventos basicamente expõem "inscrever-se" e "cancelar inscrição" e é isso.

Vale a pena conhecer o que os eventos do tipo campo estão realmente fazendo - eles estão criando uma variável e um evento ao mesmo tempo. Dentro da classe, você acaba fazendo referência à variável. Do lado de fora, você faz referência ao evento.

Veja meu artigo sobre eventos e delegados para obter mais informações.

Jon Skeet
fonte
3
Se você é teimoso, pode forçá-lo claramente através da reflexão. Consulte stackoverflow.com/questions/91778/… .
Brian
1
@ Brian: Depende da implementação. Se for apenas um evento de campo ou um EventHandlerList, você poderá. Você teria que reconhecer esses dois casos - e poderia haver várias outras implementações.
Jon Skeet
@ Josué: Não, ele definirá a variável para ter um valor nulo. Concordo que a variável não será chamada hidden.
Jon Skeet
@ JonSkeet Foi o que eu (pensei) eu disse. A forma como foi escrita me confundiu por 5 minutos.
@JoshuaLamusga: Bem, você disse que limparia uma lista de chamadas, que parece modificar um objeto existente.
Jon Skeet
34

Adicione um método a c1 que definirá 'someEvent' como nulo.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}
programador
fonte
Esse é o comportamento que estou vendo. Como eu disse na minha pergunta, não sei se estou ignorando alguma coisa.
programador
8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

É melhor usar delegate { }do nullque evitar a exceção de ref nula.

Feng
fonte
2
Por quê? Você poderia expandir esta resposta?
S. Buda
1
@ S.Buda Porque se for nulo, você receberá uma referência nula. É como usar um List.Clear()vs myList = null.
AustinWBryan 29/06
6

Definir o evento como nulo dentro da classe funciona. Quando você descarta uma classe, sempre deve definir o evento como nulo, o GC tem problemas com os eventos e pode não limpar a classe descartada se houver eventos pendentes.

Jonathan C Dickinson
fonte
6

A melhor prática para limpar todos os assinantes é definir o someEvent como null, adicionando outro método público, se você quiser expor essa funcionalidade para fora. Isso não tem consequências invisíveis. A pré-condição é lembrar de declarar SomeEvent com a palavra-chave 'event'.

Por favor, veja o livro - C # 4.0 em poucas palavras, página 125.

Alguém aqui propôs usar o Delegate.RemoveAllmétodo. Se você o usar, o código de exemplo pode seguir o formulário abaixo. Mas é realmente estúpido. Por que não apenas SomeEvent=nulldentro da ClearSubscribers()função?

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

Você pode conseguir isso usando os métodos Delegate.Remove ou Delegate.RemoveAll.

Micah
fonte
6
Não acredito que isso funcione com expressões lambda ou delegados anônimos.
programador
3

Comentário chato estendido conceitual.

Prefiro usar a palavra "manipulador de eventos" em vez de "evento" ou "delegar". E usou a palavra "evento" para outras coisas. Em algumas linguagens de programação (VB.NET, Object Pascal, Objective-C), "event" é chamado de "message" ou "signal" e até possui uma palavra-chave "message" e sintaxe específica do sugar.

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, para responder a essa "mensagem", um "manipulador de eventos" responde, seja um único delegado ou vários delegados.

Resumo: "Evento" é a "pergunta", "manipulador (es) de evento" são a resposta (s).

umlcat
fonte
1

Esta é a minha solução:

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

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

É necessário chamar Dispose()ou usar o using(new Foo()){/*...*/}padrão para cancelar a assinatura de todos os membros da lista de chamadas .

Jalal
fonte
0

Remova todos os eventos, suponha que o evento seja do tipo "Ação":

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}
Googol
fonte
1
Se você está dentro do tipo que declarou o evento, não precisa fazer isso, basta configurá-lo como nulo; se estiver fora do tipo, não poderá obter a lista de chamadas do delegado. Além disso, seu código gera uma exceção se o evento for nulo ao chamar GetInvocationList.
Servy
-1

Em vez de adicionar e remover retornos de chamada manualmente e declarar vários tipos de delegados em todos os lugares:

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

Você pode tentar esta abordagem genérica:

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
fonte
Você pode formatar sua pergunta e remover todo o espaço em branco à esquerda? Quando você copia e cola de um IDE, isso pode acontecer
AustinWBryan
Acabei de me livrar desse espaço em branco, meu mal
barthdamon