Padrões de eventos fracos (WPF .NET)

Em aplicativos, é possível que os manipuladores anexados às 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. 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 gerente 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.

Importante

A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.

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 Windows Presentation Foundation (WPF).

Por que implementar o padrão de eventos fracos?

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 origem. Por exemplo, a instrução C# ou a instrução source.SomeEvent += new SomeEventHandler(MyEventHandler)AddHandler source.SomeEvent, AddressOf MyEventHandlerVB . No entanto, essa técnica cria uma referência forte da fonte do evento para o ouvinte do evento. A menos que o manipulador de eventos seja explicitamente não 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 ú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 é 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 útil do ouvinte. Neste 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 tempo de vida do objeto do ouvinte. Na verdade, a referência implícita da fonte não determina se o ouvinte é elegível 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.

Quem deve implementar o padrão de eventos fracos?

O padrão de eventos fracos é principalmente relevante para os autores controle. Como autor de controle, você é o grande responsável pelo comportamento e contenção de seu 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 vinculação de dados, é comum que o objeto de origem seja 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á quatro maneiras de implementar o padrão de eventos 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 no qual você deseja se inscrever tiver um WeakEventManagerarquivo . 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 quando um existente WeakEventManager<TEventSource,TEventArgs>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 a partir 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 usando um arquivo existente ou personalizado 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 requer que você escreva mais código inicial.

  • Gerenciador 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 gerenciadores de eventos fracos. Muitas estruturas WPF também suportam o padrão.

As seções a seguir descrevem como implementar o padrão de eventos 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 para se 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 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 inscrição 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 do gerenciador de eventos fraco

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 EventArgs digite como os parâmetros de tipo para a classe. Chame 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 classe a seguir 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 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 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