适用于 WinRT 的 WeakEventManager






4.33/5 (2投票s)
适用于 WinRT 的 WeakEventManager
引言
当使用实例方法注册到事件时,事件源会存储对事件处理程序的引用。这通常没问题,但是存在事件源可能比事件处理程序存活时间更长的情况。我最近遇到的一个例子是,我想监听 Clipboard.ContextChanged 事件,该事件是 static
的。
在 WPF 中,你会使用 WeakEventManager<TEventSource, TEventArgs> 类,但是这个类在 Windows Store 应用中不可用,所以让我们自己构建它吧!我稍微修改了 API,因为我需要重载来接受一个 Type
,这是由于 Clipboard
是一个 static
类造成的(你不能将其用于泛型类型参数)。所以,这就是我最终用来注册事件的方式
WeakEventManager.AddHandler<object>(
typeof(Clipboard),
"ContentChanged",
this.OnClipboardContentChanged);
这是主类——实际逻辑由嵌套类执行
using System;
using System.Collections.Generic;
using System.Reflection;
/// <summary>
/// Implements a weak event listener that allows the owner to be garbage
/// collected if its only remaining link is an event handler.
/// </summary>
public static class WeakEventManager
{
private readonly static List<WeakEvent> registeredEvents = new List<WeakEvent>();
/// <summary>
/// Adds the specified event handler to the specified event.
/// </summary>
/// <typeparam name="TEventArgs">The type that holds the event data.</typeparam>
/// <param name="sourceType">
/// The type of the class that contains the specified event.
/// </param>
/// <param name="eventName">The name of the event to subscribe to.</param>
/// <param name="handler">The delegate that handles the event.</param>
public static void AddHandler<TEventArgs>
(Type sourceType, string eventName, EventHandler<TEventArgs> handler)
{
EventInfo eventInfo = sourceType.GetRuntimeEvent(eventName);
registeredEvents.Add(
new WeakEvent(null, eventInfo, handler));
}
/// <summary>
/// Adds the specified event handler to the specified event.
/// </summary>
/// <typeparam name="TEventSource">The type that raises the event.</typeparam>
/// <typeparam name="TEventArgs">The type that holds the event data.</typeparam>
/// <param name="source">
/// The source object that raises the specified event.
/// </param>
/// <param name="eventName">The name of the event to subscribe to.</param>
/// <param name="handler">The delegate that handles the event.</param>
public static void AddHandler<TEventSource, TEventArgs>
(TEventSource source, string eventName, EventHandler<TEventArgs> handler)
{
EventInfo eventInfo = typeof(TEventSource).GetRuntimeEvent(eventName);
registeredEvents.Add(
new WeakEvent(source, eventInfo, handler));
}
/// <summary>
/// Removes the specified event handler from the specified event.
/// </summary>
/// <typeparam name="TEventArgs">The type that holds the event data.</typeparam>
/// <param name="sourceType">
/// The type of the class that contains the specified event.
/// </param>
/// <param name="eventName">
/// The name of the event to remove the handler from.
/// </param>
/// <param name="handler">The delegate to remove.</param>
public static void RemoveHandler<TEventArgs>
(Type sourceType, string eventName, EventHandler<TEventArgs> handler)
{
EventInfo eventInfo = sourceType.GetRuntimeEvent(eventName);
foreach (WeakEvent weakEvent in registeredEvents)
{
if (weakEvent.IsEqualTo(null, eventInfo, handler))
{
weakEvent.Detach();
break;
}
}
}
/// <summary>
/// Removes the specified event handler from the specified event.
/// </summary>
/// <typeparam name="TEventSource">The type that raises the event.</typeparam>
/// <typeparam name="TEventArgs">The type that holds the event data.</typeparam>
/// <param name="source">
/// The source object that raises the specified event, or null if it's
/// a static event.
/// </param>
/// <param name="eventName">
/// The name of the event to remove the handler from.
/// </param>
/// <param name="handler">The delegate to remove.</param>
public static void RemoveHandler<TEventSource, TEventArgs>
(TEventSource source, string eventName, EventHandler<TEventArgs> handler)
{
EventInfo eventInfo = typeof(TEventSource).GetRuntimeEvent(eventName);
foreach (WeakEvent weakEvent in registeredEvents)
{
if (weakEvent.IsEqualTo(source, eventInfo, handler))
{
weakEvent.Detach();
break;
}
}
}
private class WeakEvent
{
// Implementation details to follow
}
}
当然,在生产环境中,你希望进行一些参数验证(例如,不为 null
,并且 GetRuntimeEvent
返回了某些内容),但是为了保持代码简单,我省略了它。实际逻辑位于 WeakEvent
嵌套类中。这是必要的,因为你不能仅仅保存对事件处理程序对象的弱引用,因为这很可能是一个临时对象,很快就会被垃圾回收。你也不能保存对事件处理程序的强引用,因为它引用了目标实例,会使其保持存活,从而违背了该类的目的!因此,WeakEvent
类必须以某种方式包装事件处理程序委托的各个部分,以便目标类可以被垃圾回收。以下是我采用的方法
private class WeakEvent
{
private static readonly MethodInfo onEventInfo =
typeof(WeakEvent).GetTypeInfo().GetDeclaredMethod("OnEvent");
private EventInfo eventInfo;
private object eventRegistration;
private MethodInfo handlerMethod;
private WeakReference<object> handlerTarget;
private object source;
public WeakEvent(object source, EventInfo eventInfo, Delegate handler)
{
this.source = source;
this.eventInfo = eventInfo;
// We can't store a reference to the handler (as that will keep
// the target alive) but we also can't store a WeakReference to
// handler, as that could be GC'd before its target.
this.handlerMethod = handler.GetMethodInfo();
this.handlerTarget = new WeakReference<object>(handler.Target);
object onEventHandler = this.CreateHandler();
this.eventRegistration = eventInfo.AddMethod.Invoke(
source,
new object[] { onEventHandler });
// If the AddMethod returned null then it was void - to
// unregister we simply need to pass in the same delegate.
if (this.eventRegistration == null)
{
this.eventRegistration = onEventHandler;
}
}
public void Detach()
{
if (this.eventInfo != null)
{
WeakEventManager.registeredEvents.Remove(this);
this.eventInfo.RemoveMethod.Invoke(
this.source,
new object[] { eventRegistration });
this.eventInfo = null;
this.eventRegistration = null;
}
}
public bool IsEqualTo(object source, EventInfo eventInfo, Delegate handler)
{
if ((source == this.source) && (eventInfo == this.eventInfo))
{
object target;
if (this.handlerTarget.TryGetTarget(out target))
{
return (handler.Target == target) &&
(handler.GetMethodInfo() == this.handlerMethod);
}
}
return false;
}
public void OnEvent<T>(object sender, T args)
{
object instance;
if (this.handlerTarget.TryGetTarget(out instance))
{
this.handlerMethod.Invoke(instance, new object[] { sender, args });
}
else
{
this.Detach();
}
}
private object CreateHandler()
{
Type eventType = this.eventInfo.EventHandlerType;
ParameterInfo[] parameters = eventType.GetTypeInfo()
.GetDeclaredMethod("Invoke")
.GetParameters();
return onEventInfo.MakeGenericMethod(parameters[1].ParameterType)
.CreateDelegate(eventType, this);
}
}
基本上,委托的各个部分存储在两个部分中;要调用的方法(存储为普通引用)和要在其上调用该方法的 target(存储为弱引用)。棘手的部分是通过反射在事件上注册我们的处理程序,这由于核心 WinRT 类中的事件与 .NET 事件略有不同而变得更加困难(例如,事件的 add
方法返回一个 EventRegistrationToken
,必须将其传递给 remove
方法;.NET 事件为 add
方法返回 void
,并且需要将传递到 add
方法的委托作为 remove
方法的参数)。当收到回调时,我们会快速检查我们的 target 是否仍然有效;如果无效,则从事件中注销(毕竟,在未来调用时我们没什么可做的!),这允许 GC 清理 WeakEvent
实例(尽管它不应该太重)。
归类于:CodeProject