Padrões de eventos fracos
Em aplicações, é possível que os manipuladores anexados a origens de eventos não sejam destruídos em coordenação com o objeto de escutador que anexou o manipulador à origem. Esta situação pode levar a fugas de memória. O Windows Presentation Foundation (WPF) introduz um padrão de design que pode ser usado para resolver esse problema, fornecendo uma classe de gerenciador 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 fraco de eventos?
Ouvir eventos pode levar a fugas de memória. A técnica típica para escutar um evento é usar a sintaxe específica da linguagem que conecta um manipulador a um evento na origem. Por exemplo, em C#, essa sintaxe é: source.SomeEvent += new SomeEventHandler(MyEventHandler)
.
Essa técnica cria uma referência forte da fonte do evento para o ouvinte do evento. 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 certas circunstâncias, você pode querer que o tempo de vida do objeto do ouvinte seja controlado por outros fatores, como se ele pertence atualmente à árvore visual do aplicativo, e não pelo tempo de vida da fonte. Sempre que o tempo de vida do objeto de origem se estende além do tempo de vida do objeto do ouvinte, o padrão de evento normal leva a um vazamento de memória: o ouvinte é mantido vivo por mais tempo do que o pretendido.
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 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 sempre que o tempo de vida do objeto da fonte exceder 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. Com efeito, a referência implícita da fonte não determina se o ouvinte é elegível para a coleta de lixo. A referência é uma referência fraca, por isso a designação do padrão de evento fraco e das APIs relacionadas. O ouvinte pode ser coletado pelo coletor de lixo ou destruído de outra forma, e a fonte pode continuar sem reter referências de manipulador não coletáveis a um objeto agora destruído.
Quem deve implementar o padrão de eventos fracos?
A implementação do padrão de evento fraco é interessante principalmente para desenvolvedores de controlos. 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 tempo de vida do objeto de controle, em particular a manipulaçã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 completamente independente do objeto ouvinte, que é um destino de uma ligaçã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
Existem três formas de implementar o padrão de evento fraco. A tabela a seguir lista as três abordagens e fornece algumas orientações sobre quando você deve usar cada uma.
Abordagem | Quando implementar |
---|---|
Usar uma classe de gerenciador de eventos fraca existente | Se o evento no qual você deseja se inscrever tiver um WeakEventManagercorrespondente , 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 classe WeakEventManager. Como os gerentes de eventos fracos incluídos são limitados, você provavelmente precisará escolher uma das outras abordagens. |
Usar uma classe genérica de gerenciador de eventos fraco | Use um WeakEventManager<TEventSource,TEventArgs> genérico quando um WeakEventManager existente não estiver disponível, você quiser uma maneira fácil de implementar e não estiver preocupado com a eficiência. O WeakEventManager<TEventSource,TEventArgs> genérico é menos eficiente do que um gerenciador de eventos fraco existente ou personalizado. Por exemplo, a classe genérica realiza mais análise para descobrir o evento com base no nome do evento. Além disso, o código para registrar o evento usando o WeakEventManager<TEventSource,TEventArgs> genérico é mais detalhado do que usar um WeakEventManagerexistente ou personalizado. |
Criar uma classe personalizada de gestor de eventos fraco | Crie um WeakEventManager personalizado quando um WeakEventManager existente não estiver disponível e você quiser a melhor eficiência. Usar um WeakEventManager personalizado 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 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 efeitos desta discussão, o evento a subscrever tem as seguintes características.
O nome do evento é
SomeEvent
.O evento é levantado pela classe
EventSource
.O manipulador de eventos tem tipo:
SomeEventEventHandler
(ouEventHandler<SomeEventEventArgs>
).O evento passa um parâmetro do tipo
SomeEventEventArgs
para os manipuladores de eventos.
Usando uma classe existente fraca do Event Manager
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 na classe WeakEventManager.
Use o novo gestor de eventos fraco em vez do encadeamento de eventos normal.
Por exemplo, se o seu código usa o seguinte padrão para se inscrever em um evento:
source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
Altere-o para o seguinte padrão:
SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
Da mesma forma, se o seu 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 a Classe Genérica do Gestor de Eventos Fracos
Use a classe genérica WeakEventManager<TEventSource,TEventArgs> em vez da ligação de evento normal.
Quando utiliza WeakEventManager<TEventSource,TEventArgs> para registar escutadores de eventos, fornece a fonte do evento e o tipo EventArgs como parâmetros de tipo para a classe e chama AddHandler, conforme mostrado no código a seguir.
WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
Criando uma Classe Personalizada de Gerenciador de Eventos Fracos
Copie o seguinte modelo de classe para o seu projeto.
Esta 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(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); } }
Substitua o nome
SomeEventWeakEventManager
pelo seu próprio nome.Substitua os três nomes descritos anteriormente pelos nomes correspondentes para o seu evento. (
SomeEvent
,EventSource
eSomeEventEventArgs
)Defina a visibilidade (pública / interna / privada) da classe fraca do gerenciador de eventos para a mesma visibilidade do evento que ela gerencia.
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 SomeEventEventHandler(OnSomeEvent);
Altere-o para o seguinte padrão:
SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
Da mesma forma, se o seu código usar o seguinte padrão para cancelar a assinatura de um evento:
source.SomeEvent -= new SomeEventEventHandler(OnSome);
Altere-o para o seguinte padrão:
SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
Ver também
- WeakEventManager
- IWeakEventListener
- Visão geral de eventos roteados
- Visão geral da vinculação de dados
.NET Desktop feedback