Compartilhar via


Padrões de eventos fracos (WPF .NET)

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

Pré-requisitos

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

Por que implementar o padrão de evento fraco?

A escuta de eventos pode levar a vazamentos de memória. A técnica usual para escutar um evento é usar a sintaxe particular da linguagem para anexar um manipulador a um evento em uma origem. Por exemplo, a instrução C# source.SomeEvent += new SomeEventHandler(MyEventHandler) ou a instrução VB AddHandler source.SomeEvent, AddressOf MyEventHandler. No entanto, essa técnica cria uma referência forte da origem do evento para o ouvinte de eventos. A menos que o manipulador de eventos não esteja 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 origem se estende além do tempo de vida útil do objeto do ouvinte, o ouvinte é mantido vivo 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 perda 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 se registre e receba o evento sem afetar as características de tempo de vida do objeto do ouvinte de qualquer forma. 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 nomenclatura do padrão de evento fraco e as APIs relacionadas. O listener (ouvinte) pode ser coletado como lixo ou destruído de outra maneira, e a origem pode continuar sem manter referências de manipuladores não coletáveis a um objeto agora destruído.

Quem deve implementar o padrão de evento fraco?

O padrão de evento fraco é relevante principalmente para os autores de controle. Como autor de controle, você é responsável principalmente pelo comportamento e pela contenção do controle e pelo impacto que ele tem nos aplicativos nos quais ele é inserido. Isso inclui o comportamento de tempo de vida do objeto do controle, em particular o tratamento do problema de vazamento de memória descrito.

Determinados cenários inerentemente se prestam à aplicação do padrão de evento fraco. Um desses cenários é 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 adapte ao seu cenário.

  • o gerenciador de eventos fracos existente:

    Use uma classe de gerenciador de eventos fraco existente quando o evento ao qual você deseja se inscrever tiver um WeakEventManagercorrespondente. Para obter uma lista de gerenciadores de eventos fracos que vêm com o WPF, consulte a hierarquia de herança na classe WeakEventManager. Como os gerentes de eventos fracos incluídos são limitados, você provavelmente precisará escolher uma das outras abordagens.

  • Gerenciador de eventos fraco genérico:

    Use um WeakEventManager<TEventSource,TEventArgs> genérico quando um WeakEventManager existente 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 fracos existente ou personalizado, pois utiliza reflection para descobrir o evento a partir de seu nome. Além disso, o código necessário para registrar o evento usando o WeakEventManager<TEventSource,TEventArgs> genérico é mais detalhado do que usar um WeakEventManagerexistente ou personalizado.

  • o gerenciador de eventos fraco personalizado:

    Crie um WeakEventManager customizado quando um WeakEventManager existente não estiver disponível e a eficiência for crucial. Embora seja mais eficiente que um WeakEventManagergenérico, um WeakEventManager personalizado exige que você escreva mais código adicional no início.

  • gerente de eventos fraco de terceiros:

    Use um gerenciador de eventos fraco de terceiros quando precisar de uma funcionalidade que não seja fornecida 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 ao qual se deve inscrever 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 para os manipuladores de eventos.

Usar uma classe de gerenciador de eventos fraca existente

  1. Localize um gerenciador de eventos fraco existente. Para obter uma lista de gerenciadores de eventos fracos incluídos no WPF, consulte a hierarquia de herança da classe WeakEventManager.

  2. Use o novo gerenciador de eventos fracos em vez da conexão de eventos normal.

    Por exemplo, se o código usar o seguinte padrão 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 seu 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

Utilize a classe WeakEventManager<TEventSource,TEventArgs> genérica ao invés da conexão de evento normal.

Quando você usa WeakEventManager<TEventSource,TEventArgs> para registrar ouvintes de eventos, fornece a origem do evento e EventArgs tipo como parâmetros de tipo para a classe. Chame AddHandler conforme mostrado no seguinte código:

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 eventos fraca personalizada

  1. Copie o modelo de classe a seguir para seu projeto. A seguinte classe herda da classe WeakEventManager:

    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 evento.

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

  4. Use o novo gerenciador de eventos fracos em vez da conexão de eventos normal.

    Por exemplo, se o seu código utilizar o seguinte padrão 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))
    

    Da mesma forma, se o código usar o seguinte padrão para cancelar a assinatura de 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))
    

Consulte também