常见的事件和委托,都包括两部分:对象和方法,当然如果委托到静态方法上,对象是为空的。

如果把事件委托到某个对象的方法上,同时就间接的引用了这个对象,导致其一直无法被回收,从而造成内存泄漏

弱引用Action,原理就是把委托拆分,然后弱引用对象部分,需要调用委托的时候,再把对象“拉”回来,如果对象被回收了,就没有必要再调用它的方法了。


Nuget包:NewLife.Core

源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Event/WeakAction.cs

视频:https://www.bilibili.com/video/BV1pg411h7So


基本用法

WeakAction设计核心在于,从函数委托中把对象和方法拆分。因此有基础的构造函数

public WeakAction(Object target, MethodInfo method);
public WeakAction(Object target, MethodInfo method, Action<Action<TArgs>> unHandler, Boolean once);

直接传入目标对象和方法,很容易理解,但是很生硬,用起来也诸多不便。还需要有个传入委托的构造函数

public WeakAction(Delegate handler);
public WeakAction(Delegate handler, Action<Action<TArgs>> unHandler, Boolean once);

Delegate参数意味着可以传入任意委托。

UnHandler用于在对象已经被回收时,额外执行的一个动作,一般用来解除事件绑定。

Once 表示该弱引用委托只能使用一次,然后就会回收。


核心原理

WeakAction在执行Invoke时,首先通过弱引用取得对象,如果对象已经被GC回收,此时将会取得空值。否则,执行委托方法触发被绑定的事件。

/// <summary>调用委托</summary>
/// <param name="e"></param>
public void Invoke(TArgs e)
{
    //if (!Target.IsAlive) return;
    // Keep in mind that,不要用上面的写法,因为判断可能通过,但是接着就被GC回收了,如果判断Target,则会增加引用
    Object target = null;
    if (Target == null)
    {
        if (Method.IsStatic) Reflect.Invoke(null, Method, e);
    }
    else
    {
        target = Target.Target;
        if (target != null)
        {
            // 优先使用委托
            if (Method is MethodInfo mi)
                mi.As<Action<TArgs>>(target).Invoke(e);
            else
                target.Invoke(Method, e);
        }
    }

    // 调用方已被回收,或者该事件只使用一次,则取消注册
    if ((Target != null && target == null || Once) && UnHandler != null)
    {
        UnHandler(Handler);
        UnHandler = null;
    }
}


总结

WeakAction的核心功用是规避事件型内存泄漏,让事件方法所在对象不受牵挂的能够被GC回收。