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

xiaoxiao2021-02-28  30

  欢迎参与讨论,转载请注明出处。   本文转载自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的庇护下对此更为麻痹。一般需要定期使用专业工具进行检测,这也是优化的一环啊。

转载请注明原文地址: https://www.6miu.com/read-2628557.html

最新回复(0)