Partilhar via


Padrões de eventos fracos (WPF .NET)

Em aplicações, é possível que os manipuladores ligados a fontes de eventos possam não ser destruídos em coordenação com o objeto ouvinte que ligou o manipulador à fonte. Esta situação pode levar a fugas de memória. Windows Presentation Foundation (WPF) introduz 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 nos 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 de eventos roteados e que você leu visão geral de eventos roteados. Para seguir os exemplos neste artigo, isso ajuda se você estiver familiarizado com Extensible Application Markup Language (XAML) e souber como escrever aplicativos Windows Presentation Foundation (WPF).

Por que implementar o padrão de evento fraco?

Ouvir eventos pode levar a fugas de memória. A técnica usual para ouvir um evento é usar sintaxe específica da linguagem para anexar um manipulador a um evento em uma fonte. 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 fonte 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, você pode desejar 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 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 fonte excede o tempo de vida útil do objeto do ouvinte. Neste caso, útil é determinado por si. O padrão de evento fraco permite que o ouvinte se registre e receba o evento sem afetar as características do tempo de vida do objeto do ouvinte de forma alguma. 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 denominação do padrão de evento fraco e das APIs relacionadas. O ouvinte pode ser recolhido ou destruído de outra forma, e a fonte pode continuar sem reter referências de gestores não coletáveis a um objeto agora destruído.

Quem deve implementar o padrão de eventos fracos?

O padrão de eventos fracos é principalmente relevante para os desenvolvedores de controle. Como autor de controle, você é o grande responsável pelo comportamento e contenção do seu controle e pelo impacto que ele tem nos aplicativos nos quais ele está inserido. Isso inclui o comportamento do ciclo de vida do objeto do controle, em particular a gestão do problema de vazamento de memória descrito.

Certos cenários prestam-se inerentemente à aplicação do padrão de eventos fracos. Um desses cenários é a vinculaçã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 aspetos 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 evento fraco, e cada abordagem usa um gerenciador de eventos diferente. Selecione o gestor de eventos que melhor se adapta ao seu cenário.

  • Gestor de eventos fraco existente:

    Use uma classe de gerenciador de eventos fraca existente quando o evento que você deseja inscrever tiver uma WeakEventManagercorrespondente. Para obter uma lista de gerenciadores de eventos fracos incluídos no 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.

  • Gestor de eventos débil 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 WeakEventManager<TEventSource,TEventArgs> genérico é menos eficiente do que o gestor de eventos fraco existente ou personalizado, pois utiliza reflection para descobrir o evento a partir do 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 usando um WeakEventManagerexistente ou personalizado.

  • Gerenciador de eventos fraco personalizado:

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

  • Gerenciador de eventos fracos de terceiros:

    Use um gerenciador de eventos fraco de terceiros quando precisar de funcionalidades que não são fornecidas pelas outras abordagens. O NuGet tem alguns fracos gerentes de eventos. Muitas estruturas WPF também suportam o 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 gestor de eventos fracos, o evento a que se deve subscrever tem as seguintes características.

  • O nome do evento é SomeEvent.
  • O evento é levantado 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. Encontre 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 fraco em vez da conexão de evento normal.

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

Use a classe genérica do gerenciador de eventos fracos

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

Quando se utiliza WeakEventManager<TEventSource,TEventArgs> para registar escutadores de eventos, fornece-se a fonte do evento e o tipo EventArgs como 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 gestor personalizada para eventos fracos

  1. Copie o seguinte modelo de classe para o 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 fraca do gerenciador de eventos para corresponder à acessibilidade do evento que ela gerencia.

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

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

Ver também