[fix]GetNext
大石头 编写于 2024-06-25 16:49:36
X
using System.Reflection;
using NewLife.Reflection;

namespace NewLife;

/// <summary>弱引用Action</summary>
/// <remarks>
/// 常见的事件和委托,都包括两部分:对象和方法,当然如果委托到静态方法上,对象是为空的。
/// 如果把事件委托到某个对象的方法上,同时就间接的引用了这个对象,导致其一直无法被回收,从而造成内存泄漏。
/// 弱引用Action,原理就是把委托拆分,然后弱引用对象部分,需要调用委托的时候,再把对象“拉”回来,如果被回收了,就没有必要再调用它的方法了。
/// 
/// 文档 https://newlifex.com/core/weak_action
/// </remarks>
/// <typeparam name="TArgs"></typeparam>
public class WeakAction<TArgs>
{
    #region 属性
    /// <summary>目标对象。弱引用,使得调用方对象可以被GC回收</summary>
    readonly WeakReference? Target;

    /// <summary>委托方法</summary>
    readonly MethodBase Method;

    /// <summary>经过包装的新的委托</summary>
    readonly Action<TArgs> Handler;

    /// <summary>取消注册的委托</summary>
    Action<Action<TArgs>>? UnHandler;

    /// <summary>是否只使用一次,如果只使用一次,执行委托后马上取消注册</summary>
    readonly Boolean Once;
    #endregion

    #region 扩展属性
    /// <summary>是否可用</summary>
    public Boolean IsAlive
    {
        get
        {
            var target = Target;
            if (target == null && Method.IsStatic) return true;

            return target != null && target.IsAlive;
        }
    }
    #endregion

    #region 构造
    /// <summary>实例化</summary>
    /// <param name="target">目标对象</param>
    /// <param name="method">目标方法</param>
    public WeakAction(Object? target, MethodInfo method) : this(target, method, null, false) { }

    /// <summary>实例化</summary>
    /// <param name="target">目标对象</param>
    /// <param name="method">目标方法</param>
    /// <param name="unHandler">取消注册回调</param>
    /// <param name="once">是否一次性事件</param>
    public WeakAction(Object? target, MethodInfo method, Action<Action<TArgs>>? unHandler, Boolean once)
    {
        if (target != null)
        {
            Target = new WeakReference(target);
        }
        else
        {
            if (!method.IsStatic) throw new InvalidOperationException("Illegal event, no specified class instance and not a static method!");
        }

        Method = method;
        Handler = Invoke;
        UnHandler = unHandler;
        Once = once;
    }

    /// <summary>实例化</summary>
    /// <param name="handler">事件处理器</param>
    public WeakAction(Delegate handler) : this(handler.Target, handler.Method, null, false) { }

    /// <summary>使用事件处理器、取消注册回调、是否一次性事件来初始化</summary>
    /// <param name="handler">事件处理器</param>
    /// <param name="unHandler">取消注册回调</param>
    /// <param name="once">是否一次性事件</param>
    public WeakAction(Delegate handler, Action<Action<TArgs>> unHandler, Boolean once) : this(handler.Target, handler.Method, unHandler, once) { }
    #endregion

    #region 方法
    /// <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;
        }
    }

    /// <summary>把弱引用事件处理器转换为普通事件处理器</summary>
    /// <param name="handler"></param>
    /// <returns></returns>
    public static implicit operator Action<TArgs>(WeakAction<TArgs> handler) => handler.Handler;
    #endregion

    #region 辅助
    /// <summary>已重载</summary>
    /// <returns></returns>
    public override String? ToString()
    {
        if (Method == null) return base.ToString();

        if (Method.DeclaringType != null)
            return $"{Method.DeclaringType.Name}.{Method.Name}";
        else
            return Method.Name;
    }
    #endregion
}