Padrões de evento fracos (WPF .NET)

Em aplicativos, é possível que os manipuladores anexados a fontes de eventos não sejam destruídos em coordenação com o objeto 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. O padrão de design fornece uma classe de gerenciador dedicada para eventos específicos e implementa uma interface em ouvintes para esse evento. Esse padrão de design é conhecido como o padrão de evento fraco.

Pré-requisitos

O artigo pressupõe um conhecimento básico de eventos roteados e que você leu Visão geral de eventos roteados. Para seguir os exemplos neste artigo, é útil se você estiver familiarizado com XAML (Extensible Application Markup Language) e souber como escrever aplicativos WPF (Windows Presentation Foundation).

Por que implementar o padrão de evento fraco?

Escutar eventos pode levar a vazamentos de memória. A técnica usual para ouvir um evento é usar a sintaxe específica da linguagem para anexar um manipulador a um evento em uma fonte. Por exemplo, a instrução source.SomeEvent += new SomeEventHandler(MyEventHandler) C# ou a instrução AddHandler source.SomeEvent, AddressOf MyEventHandlerVB . No entanto, essa técnica cria uma referência forte da origem do evento para o ouvinte do evento. A menos que o manipulador de eventos não seja explicitamente registrado, o tempo de vida do objeto do ouvinte será influenciado pelo tempo de vida do objeto da origem. Em determinadas circunstâncias, talvez você queira que o tempo de vida do objeto do ouvinte seja controlado por outros fatores, como se ele pertence atualmente à árvore visual do aplicativo. Sempre que o tempo de vida do objeto da fonte se estende além do tempo de vida útil do objeto do ouvinte, o ouvinte é mantido ativo por mais tempo do que o necessário. Nesse caso, a memória não alocada equivale a um vazamento de memória.

O padrão de evento fraco foi projetado para resolver o problema de vazamento de memória. O padrão de evento fraco pode ser usado quando um ouvinte precisa se registrar para um evento, mas o ouvinte não sabe explicitamente quando cancelar o registro. O padrão de evento fraco também pode ser usado quando o tempo de vida do objeto da origem excede o tempo de vida útil do objeto do ouvinte. Nesse caso, ú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 tempo de vida do objeto do ouvinte. Na verdade, a referência implícita da origem não determina se o ouvinte está qualificado para a coleta de lixo. A referência é uma referência fraca, portanto, a nomenclatura 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.

Quem deve implementar o padrão de evento fraco?

O padrão de eventos fraco é relevante principalmente para os autores de controle. Como autor do controle, você é em grande parte responsável pelo comportamento e contenção do controle e pelo impacto que ele tem nos aplicativos nos quais ele está inserido. Isso inclui o comportamento do tempo de vida do objeto do 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 independente do objeto ouvinte, que é um destino de uma associação. Muitos aspectos da associaçã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á quatro maneiras de implementar o padrão de evento fraco, e cada abordagem usa um gerenciador de eventos diferente. Selecione o gerenciador de eventos que melhor se adapta ao seu cenário.

  • Gerenciador de eventos fraco existente:

    Use uma classe de gerenciador de eventos fraca existente quando o evento que você deseja assinar tiver um WeakEventManager. 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.

  • Gerenciador de eventos fraco genérico:

    Use um genérico WeakEventManager<TEventSource,TEventArgs> quando um existente WeakEventManager não estiver disponível e você estiver procurando a maneira mais fácil de implementar eventos fracos. No entanto, o genérico WeakEventManager<TEventSource,TEventArgs> é menos eficiente do que o gerenciador de eventos fraco existente ou personalizado porque usa a reflexão para descobrir o evento de seu nome. Além disso, o código necessário para registrar o evento usando o genérico WeakEventManager<TEventSource,TEventArgs> é mais detalhado do que usar um arquivo .WeakEventManager

  • Gerenciador de eventos fraco personalizado:

    Crie um personalizado WeakEventManager quando um existente WeakEventManager não estiver disponível e a eficiência for crucial. Embora mais eficiente do que um genérico WeakEventManager, um personalizado WeakEventManager exige que você escreva mais código inicial.

  • Gerenciador de eventos fraco de terceiros:

    Use um gerenciador de eventos fraco de terceiros quando precisar de funcionalidades que não sejam fornecidas pelas outras abordagens. O NuGet tem alguns gerentes de eventos fracos. Muitas estruturas do WPF também dão suporte ao padrão.

As seções a seguir descrevem como implementar o padrão de evento fraco por meio do uso dos diferentes tipos de gerenciador de eventos. Para os exemplos genéricos e personalizados do gerenciador de eventos fracos, o evento a ser assinado tem as seguintes características.

  • O nome do evento é SomeEvent.
  • O evento é gerado pela classe SomeEventSource.
  • O manipulador de eventos tem o tipo EventHandler<SomeEventArgs>.
  • O evento passa um parâmetro do tipo SomeEventArgs aos manipuladores de eventos.

Usar uma classe existente de gerenciamento 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 da 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.LostFocus += new RoutedEventHandler(Source_LostFocus);
    
    AddHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Altere-o para o seguinte padrão:

    LostFocusEventManager.AddHandler(source, Source_LostFocus);
    
    LostFocusEventManager.AddHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

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

    source.LostFocus -= new RoutedEventHandler(Source_LostFocus);
    
    RemoveHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Altere-o para o seguinte padrão:

    LostFocusEventManager.RemoveHandler(source, Source_LostFocus);
    
    LostFocusEventManager.RemoveHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

Usar a classe genérica de gerenciador de eventos fraco

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

Ao usar WeakEventManager<TEventSource,TEventArgs> o para registrar ouvintes de eventos, você fornece a origem e EventArgs o tipo do evento como os parâmetros de tipo para a classe. Chamada AddHandler conforme mostrado no código a seguir:

WeakEventManager<SomeEventSource, SomeEventArgs>.AddHandler(source, "SomeEvent", Source_SomeEvent);
WeakEventManager(Of SomeEventSource, SomeEventArgs).AddHandler(
    source, "SomeEvent", New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))

Criar uma classe de gerenciador de evento fraco personalizado

  1. Copie o seguinte modelo de classe no seu projeto. A seguinte 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(SomeEventSource source,
                                      EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(SomeEventSource source,
                                         EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(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<SomeEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
    Class SomeEventWeakEventManager
        Inherits WeakEventManager
    
        Private Sub New()
        End Sub
    
        ''' <summary>
        ''' Add a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [AddHandler](source As SomeEventSource,
                                       handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedAddHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Remove a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [RemoveHandler](source As SomeEventSource,
                                          handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedRemoveHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Get the event manager for the current thread.
        ''' </summary>
        Private Shared ReadOnly Property CurrentManager As SomeEventWeakEventManager
            Get
                Dim managerType As Type = GetType(SomeEventWeakEventManager)
                Dim manager As SomeEventWeakEventManager =
                    CType(GetCurrentManager(managerType), SomeEventWeakEventManager)
    
                If manager Is Nothing Then
                    manager = New SomeEventWeakEventManager()
                    SetCurrentManager(managerType, manager)
                End If
    
                Return manager
            End Get
        End Property
    
        ''' <summary>
        ''' Return a new list to hold listeners to the event.
        ''' </summary>
        Protected Overrides Function NewListenerList() As ListenerList
            Return New ListenerList(Of SomeEventArgs)()
        End Function
    
        ''' <summary>
        ''' Listen to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StartListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Stop listening to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StopListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Event handler for the SomeEvent event.
        ''' </summary>
        Private Sub OnSomeEvent(sender As Object, e As SomeEventArgs)
            DeliverEvent(sender, e)
        End Sub
    End Class
    
  2. Renomeie SomeEventWeakEventManager, SomeEvent, SomeEventSourcee SomeEventArgs para corresponder ao nome do seu evento.

  3. Defina os modificadores de acesso para a classe de gerenciador de eventos fraca para corresponder à acessibilidade do evento que ela gerencia.

  4. 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 EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    AddHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Altere-o para o seguinte padrão:

    SomeEventWeakEventManager.AddHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.AddHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

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

    source.SomeEvent -= new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    RemoveHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Altere-o para o seguinte padrão:

    SomeEventWeakEventManager.RemoveHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.RemoveHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

Confira também