Padrões de evento fraco

Em aplicativos, é possível que manipuladores que estão anexados a origens de eventos não sejam destruídos em coordenação com o objeto de ouvinte que anexou o manipulador à origem. Essa situação pode levar a vazamentos de memória. O Windows Presentation Foundation (WPF) apresenta um padrão de design que pode ser usado para resolver esse problema, fornecendo uma classe de gerente dedicada para eventos específicos e implementando uma interface em ouvintes para esse evento. Esse padrão de design é conhecido como o padrão de evento fraco.

Por que implementar o padrão de evento fraco?

Escutar eventos pode levar a vazamentos de memória. A técnica comum para ouvir um evento é usar a sintaxe específica a um idioma que anexa um manipulador a um evento em uma fonte. Por exemplo, em C#, essa sintaxe é: source.SomeEvent += new SomeEventHandler(MyEventHandler).

Essa técnica cria uma referência forte da origem do evento para o ouvinte de eventos. Normalmente, anexar um manipulador de eventos para um ouvinte faz com que o ouvinte tenha um tempo de vida do objeto que é influenciado pelo tempo de vida do objeto da origem (a menos que o manipulador de eventos seja explicitamente removido). Mas, em determinadas circunstâncias, você pode desejar que o tempo de vida do objeto do ouvinte seja controlado por outros fatores, como se ele pertencesse à árvore visual do aplicativo e não pelo tempo de vida de origem. Sempre que o tempo de vida do objeto de origem ultrapassa o tempo de vida do objeto do ouvinte, o padrão de eventos normal ocasiona um vazamento de memória: o ouvinte é mantido ativo mais que o previsto.

O padrão de evento fraco é projetado para resolver esse problema de vazamento de memória. O padrão de evento fraco pode ser usado sempre que um ouvinte precisa registrar um evento, mas não sabe explicitamente quando cancelar o registro. O padrão de evento fraco também pode ser usado sempre que o tempo de vida do objeto de origem exceder o tempo de vida útil do objeto do ouvinte. (Nesse caso, o útil é determinado por você.) O padrão de evento fraco permite que o ouvinte registre e receba o evento sem afetar as características de vida útil do ouvinte de forma alguma. Na verdade, a referência implícita da origem não determina se o ouvinte está qualificado para coleta de lixo. A referência é uma referência fraca, portanto, a nomeação do padrão de evento fraco e das APIs relacionadas. O ouvinte pode ter o lixo coletado ou destruído, e a origem pode continuar sem reter manipulador referências não colecionáveis do manipulador para um objeto agora destruído.

Por que implementar o padrão de evento fraco?

Implementar o padrão de evento fraco é interessante principalmente para autores de controle. Como um autor de controle, você é basicamente responsável pelo comportamento e confinamento de seu controle e o impacto em aplicativos em que ele é inserido. Isso inclui o comportamento de tempo de vida do objeto de controle, em particular, o tratamento do problema de vazamento de memória descrito.

Determinados cenários prestam-se inerentemente ao aplicativo do padrão de evento fraco. Um cenário é a associação de dados. Na associação de dados, é comum que o objeto de origem seja completamente independente do objeto de ouvinte, que é um destino de uma associação. Muitos aspectos da vinculação de dados do WPF já têm o padrão de evento fraco aplicado na forma como os eventos são implementados.

Como implementar o padrão de evento fraco

Há três maneiras de implementar o padrão de evento fraco. A tabela a seguir lista as três abordagens e fornece algumas diretrizes sobre quando usar cada uma.

Abordagem Quando implementar
Usar uma classe existente de gerenciamento de evento fraco Se o evento no qual você deseja se inscrever tiver um WeakEventManager, use o gerenciador de eventos fraco existente. Para obter uma lista de gerenciadores de eventos fracos incluídos no WPF, consulte a hierarquia de herança na WeakEventManager classe. Como os gerenciadores de eventos fracos incluídos são limitados, você provavelmente precisará escolher uma das outras abordagens.
Usar uma classe de gerenciador de evento fraco genérico Use um genérico quando um existente WeakEventManager<TEventSource,TEventArgs>WeakEventManager não estiver disponível, você quer uma maneira fácil de implementar e não está preocupado com a eficiência. O genérico WeakEventManager<TEventSource,TEventArgs> é menos eficiente do que um gerenciador de eventos fraco existente ou personalizado. Por exemplo, a classe genérica faz mais reflexão para descobrir o evento que recebeu o nome do evento. Além disso, o código para registrar o evento usando o genérico WeakEventManager<TEventSource,TEventArgs> é mais detalhado do que usando um arquivo existente ou personalizado WeakEventManager.
Criar uma classe de gerenciador de evento fraco personalizado Crie um personalizado WeakEventManager quando um existente WeakEventManager não estiver disponível e você quiser a melhor eficiência. Usar um personalizado WeakEventManager para se inscrever em um evento será mais eficiente, mas você incorre no custo de escrever mais código no início.
Usar um gerenciador de eventos fraco de terceiros O NuGet tem vários gerenciadores de eventos fracos e muitas estruturas WPF também suportam o padrão.

As seções a seguir descrevem como implementar o padrão de evento fraco. Para fins desta discussão, o evento que deve ser assinado tem as seguintes características.

  • O nome do evento é SomeEvent.

  • O evento é gerado pela classe EventSource.

  • O manipulador de eventos tem o tipo: SomeEventEventHandler (ou EventHandler<SomeEventEventArgs>).

  • O evento passa um parâmetro do tipo SomeEventEventArgs aos manipuladores de eventos.

Usar uma classe existente de gerenciador de evento fraco

  1. Encontre um gerenciador de evento fraco existente.

    Para obter uma lista de gerenciadores de eventos fracos incluídos no WPF, consulte a hierarquia de herança na WeakEventManager classe.

  2. Use o novo gerenciador de evento fraco em vez da conexão de evento normal.

    Por exemplo, se seu código usa o padrão a seguir para assinar um evento:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Altere-o para o seguinte padrão:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Da mesma forma, se o código usar o seguinte padrão para cancelar a inscrição de um evento:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    Altere-o para o seguinte padrão:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Usando uma classe de gerenciador de evento fraco genérico

  1. Use a classe genérica WeakEventManager<TEventSource,TEventArgs> em vez da conexão de evento normal.

    Quando você usa WeakEventManager<TEventSource,TEventArgs> para registrar ouvintes de eventos, você fornece a fonte de evento e digite como os parâmetros de tipo para a classe e EventArgs chamada AddHandler conforme mostrado no código a seguir:

    WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
    

Criando uma classe de gerenciador de evento fraco personalizado

  1. Copie o seguinte modelo de classe no seu projeto.

    Essa classe herda da WeakEventManager classe.

    class SomeEventWeakEventManager : WeakEventManager
    {
    
        private SomeEventWeakEventManager()
        {
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(EventSource source,
                                      EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(EventSource source,
                                         EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedRemoveHandler(source, handler);
        }
    
        /// <summary>
        /// Get the event manager for the current thread.
        /// </summary>
        private static SomeEventWeakEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(SomeEventWeakEventManager);
                SomeEventWeakEventManager manager =
                    (SomeEventWeakEventManager)GetCurrentManager(managerType);
    
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new SomeEventWeakEventManager();
                    SetCurrentManager(managerType, manager);
                }
    
                return manager;
            }
        }
    
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<SomeEventEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
  2. Substitua o nome SomeEventWeakEventManager pelo seu próprio nome.

  3. Substitua os três nomes descritos anteriormente pelos nomes correspondentes para seu evento. (SomeEvent, EventSource e SomeEventEventArgs)

  4. Defina a visibilidade (pública/interna/privada) da classe de gerenciador de evento fraco para a mesma visibilidade que o evento que ele gerencia.

  5. Use o novo gerenciador de evento fraco em vez da conexão de evento normal.

    Por exemplo, se seu código usa o padrão a seguir para assinar um evento:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Altere-o para o seguinte padrão:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    De modo semelhante, se seu código usa o padrão a seguir para assinar um evento:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Altere-o para o seguinte padrão:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Confira também