C#之弱事件(Weak Event)的实现

  欢迎参与讨论,转载请注明出处。
  本文转载自https://musoucrow.github.io/2018/02/18/weak_event_csharp/

前言

  最近使用C#开发项目时,发现一个会导致内存泄漏的陷阱——event里的成员并非弱引用,这样便会导致与event相关联的对象都不会被回收,从而导致内存泄漏。如此便很有必要实现一款弱事件(Weak Event)以解决此问题。

分析

  首先当然是找找是否存在现成的方案,答案是有的,不过很奇怪的是,该解决方案隶属于WPF,那么便没戏了。从网上来看也有不少各自的实现,不过个人对此都不算太满意,于是便打算自己造个轮子。
  实现弱事件自然需要用到弱引用,而弱引用的具体实现则是WeakReference,可以根据Delegate提供的Target作为弱引用对象,Method作为调用。
  剩下的问题便是Delegate的参数问题了,很可惜Delegate似乎不支持作为泛型,但是Delegate的参数还是支持的。但即便是支持,也不方便作为多个参数来进行了。那么只能选择继承EventArgs了,EventArgs本身是个空类,一般做法是继承它然后自定义,这也是微软官方所推荐的做法。

实现

using System;
using System.Reflection;
using System.Collections.Generic;

public class WeakEvent<TEventArgs> where TEventArgs : EventArgs {
    public delegate void Func(TEventArgs e);
    private static object[] ARGS = new object[1];

    private class Unit {
        private WeakReference reference;
        private MethodInfo method;
        private bool isStatic;

        public bool IsDead {
            get {
                return !this.isStatic && !this.reference.IsAlive;
            }
        }

        public Unit(Func callback) {
            this.isStatic = callback.Target == null;
            this.reference = new WeakReference(callback.Target);
            this.method = callback.Method;
        }

        public bool Equals(Func callback) {
            return this.reference.Target == callback.Target && this.method == callback.Method;
        }

        public void Invoke(object[] args) {
            this.method.Invoke(this.reference.Target, args);
        }
    }    

    private List<Unit> list = new List<Unit>();

    public int Count {
        get {
            return this.list.Count;
        }
    }

    public void Add(Func callback) {
        this.list.Add(new Unit(callback));
    }

    public void Remove(Func callback) {
        for (int i = this.list.Count - 1; i > -1; i--) {
            if (this.list[i].Equals(callback)) {
                this.list.RemoveAt(i);
            }
        }
    }

    public void Invoke(TEventArgs args=null) {
        ARGS[0] = args;

        for (int i = this.list.Count - 1; i > -1; i--) {
            if (this.list[i].IsDead) {
                this.list.RemoveAt(i);
            }
            else {
                this.list[i].Invoke(ARGS);
            }
        }
    }

    public void Clear() {
        this.list.Clear();
    }
}

  以上便是弱事件的实现代码了,其实原理与Caller基本一致。接下来是演示:

using System;

public class Obj {
    public void Do(EventArgs e) {
        Console.WriteLine("test");
    }

    public static void StaticDo(EventArgs e) {
        Console.WriteLine("static");
    }

    public static void Main(string[] args) {
        var a = new Obj();
        var b = new Obj();
        var weakEvent = new WeakEvent<EventArgs>();

        weakEvent.Add(a.Do);
        weakEvent.Add(b.Do);
        weakEvent.Add(StaticDo);
        weakEvent.Add((EventArgs e) => Console.WriteLine("lambda"));

        a = null;
        weakEvent.Remove(StaticDo);
        GC.Collect();

        weakEvent.Invoke();
        Console.WriteLine(weakEvent.Count);
    }
}

  输出结果为:

lambda
test
2

  以上分别演示了静态方法、实例方法、匿名方法,其中静态方法和匿名方法需要手动调用Remove将之移除,如演示一般那样匿名方法便无从回收了,这点需要注意。如此弱事件便完成了,当然它带来了一定的性能损耗,这是无可避免的。也并未经过长久实践的磨砺,可以说只是一个原型罢了。

后记

  类似这样的内存泄漏问题在开发过程中可有不少,尤其是有了GC的庇护下对此更为麻痹。一般需要定期使用专业工具进行检测,这也是优化的一环啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值