C# 事件总线(弱订阅)

2018/02/11 桌面 C# WeakEvent WPF
  本文为「原创」内容,如需转载请注明出处!             
本文共 8134 字,需 101 分钟阅读

普通订阅

对于 C# 而言实现消息订阅尤为简单,只需要简单的使用委托即可,比如:

public class Subscriber1{
    public Action<string> actions;

    public Action AddListener(Action<string> action){
        actions += action;
        return ()=>{
            actions -= action;
        };
    }

    public void Raise(string message){
        actions?.Invoke(message);
    }

    public void Test(){
        var rmLsr = AddListener(msg=>{
                Console.WriteLine("test received message: "+msg);
        });
        Raise("Hello world");
        rmLsr();
        Raise("Goodbay");
        //output:
        //test received message: Hello worl
    }
}

弱订阅

上述的 AddListener 为强引用,即被引用的 action 如果为某一对象的属性,那么该对象就会被 EventSouce 强引用了,即使将被引用的对象置空也不能使之被回收掉,所以这里需要自己手动取消订阅即 rmLsr(),在 .Net4.5 以上的版本引入了 WeakEventManager<TSource,TArags> 实现对事件的弱引用。上面的版本改进如下:

弱订阅就必须使用 event 且格式必须是 Action<object,EventArgs> 这种 Windows 编程常用的事件触发模式,相对繁琐,不过也很简单

   /// <summary>
    /// event 模式的事件源
    /// </summary>
    public class SubEventSource : EventSource {
        public event Action<object, SubEventArgs> Event;

        public void Raise(SubEventArgs args) {
            Event?.Invoke(this, args);
        }
    }

    /// <summary>
    /// event 模式的事件参数
    /// </summary>
    public class SubEventArgs : EventArgs {
        public object Data;
    }

    public class Subscriber2 {
        /// <summary>
        /// 事件源
        /// </summary>
        private readonly SubEventSource evnetSource = new SubEventSource();
        /// <summary>
        /// 添加订阅,可不必手动取消
        /// </summary>
        /// <param name="handler"></param>
        /// <returns></returns>
        public Action AddListener(EventHandler<SubEventArgs> handler) {
            //注意第二个参数,该订阅使用的反射通过字符串去找到 evnetSource.Event 这个事件
            WeakEventManager<SubEventSource, SubEventArgs>.AddHandler(evnetSource, 
                nameof(evnetSource.Event), handler);
            //手动取消订阅
            return () => {
                WeakEventManager<SubEventSource, SubEventArgs>.RemoveHandler(evnetSource, 
                 nameof(evnetSource.Event),
                    handler);
            };
        }
        /// <summary>
        /// 测试自动取消订阅
        /// </summary>
        public void Test() {
            var handler = new TestHandler();
            AddListener(handler.Handle);
            evnetSource.Raise(new SubEventArgs() { Data = "hello" });
            handler = null;
            //垃圾回收
            GC.Collect();
            GC.WaitForFullGCComplete();
            evnetSource.Raise(new SubEventArgs() { Data = "world" });
            //output:
            //hello
        }

        /// <summary>
        /// 事件处理方
        /// </summary>
        public class TestHandler {
            public void Handle(object sender, SubEventArgs args) {
                Console.WriteLine(args?.Data?.ToString());
            }
        }
    }

事件总线

基于上述的事件弱订阅模式,这里实现了一个消息总线,在 WPF 中使用起来尤为方便,从此不再需要各种 Service 注入,只需要简单的 Subscribe/Dispatch 就行了

各个订阅者依赖于 Payload 的 Type 来区分各个事件,类似于 MvvmLight 的 Messenger

使用:

public class Cmd {
    public string Data;
}

public class App {
    public static readonly YEventStore EventStore = new YEventStore();
    public static void Main(string args[]){
       EventStore.Subscribe(typeof(Cmd),(s,e)=>{
            var cmd = e.Payload as Cmd;
            Console.WriteLine(cmd.Data);
        })
       EventStore.Dispatch(new Cmd(){Data="hello"});
    }
}

实现:

    public delegate void Unsubscibe();
    /// <summary>
    /// 事件总线模式的一种实现
    /// [x] 使用的是 WeakEvent ,支持(手动/自动)取消订阅
    /// [x] 所有操作都是同步的,所以注意每个操作不要耗费太时间和死锁问题
    /// <author>ychost</author>
    /// <date>2018-2-10</date>
    /// </summary>
    public class YEventStore {
        /// <summary>
        /// 所有操作上的锁
        /// </summary>
        private readonly object locker = new object();
        /// <summary>
        /// 所有事件源代
        /// </summary>
        private readonly IDictionary<Type, YEventSource> eventSource;
        /// <summary>
        /// 最近 Dispatch 的对象
        /// </summary>
        private object latestPayload;
        /// <summary>
        /// 初始化
        /// </summary>
        public YEventStore() {
            eventSource = new Dictionary<Type, YEventSource>();
        }

        /// <summary>
        /// 派遣事件
        /// </summary>
        /// <param name="payload"></param>
        public void Dispatch(object payload) {
            if (payload == null) {
                return;
            }
            var type = payload.GetType();
            lock (locker) {
                latestPayload = payload;
                if (eventSource.TryGetValue(type, out var source)) {
                    StackTrace trace = new StackTrace();
                    StackFrame frame = trace.GetFrame(1);
                    source.CallFrame = frame;
                    source.RaiseEvent(new YEventArgs(payload));
                }
            }
        }

        /// <summary>
        /// 单一订阅
        /// </summary>
        /// <param name="actionType"></param>
        /// <param name="handler"></param>
        /// <param name="useLatestPayload"></param>
        /// <returns></returns>
        public Unsubscibe Subscribe(Type actionType, EventHandler<YEventArgs> handler,
                                    bool useLatestPayload = true) {
            lock (locker) {
                if (!eventSource.ContainsKey(actionType)) {
                    eventSource[actionType] = new YEventSource();
                }
                var source = eventSource[actionType];
                WeakEventManager<YEventSource, YEventArgs>.AddHandler(source, 
                                        nameof(YEventSource.Event), handler);

                if (useLatestPayload && latestPayload?.GetType() == actionType) {
                    handler?.Invoke(new YEventSource() {
                                        CallFrame = new StackTrace().GetFrame(0)
                                    },
                          new YEventArgs(latestPayload));
                }
            }
            return () => {
                lock (locker) {
                    var source = eventSource[actionType];
                    WeakEventManager<YEventSource, YEventArgs>.RemoveHandler(source, 
                                            nameof(YEventSource.Event), handler);
                }
            };
        }

        /// <summary>
        /// 集合订阅
        /// </summary>
        /// <param name="handlers"></param>
        /// <param name="useLatestPayload"></param>
        /// <returns></returns>
        public Unsubscibe Subscribe(IDictionary<Type, EventHandler<YEventArgs>> handlers,
                                    bool useLatestPayload = true) {
            lock (locker) {
                foreach (var pair in handlers) {
                    var actionType = pair.Key;
                    var handler = pair.Value;
                    if (!eventSource.ContainsKey(actionType)) {
                        eventSource[actionType] = new YEventSource();
                    }
                    var source = eventSource[actionType];
                    WeakEventManager<YEventSource, YEventArgs>.AddHandler(source, 
                                                    nameof(YEventSource.Event), handler);

                    if (useLatestPayload && latestPayload?.GetType() == actionType) {
                        handler?.Invoke(this, new YEventArgs(latestPayload));
                    }
                }
            }
            return () => {
                lock (locker) {
                    foreach (var pair in handlers) {
                        var source = eventSource[pair.Key];
                        WeakEventManager<YEventSource, YEventArgs>.RemoveHandler(source, 
                                                nameof(YEventSource.Event), pair.Value);
                    }
                }
            };
        }
      
    }

参考文献:

Weak Event Patterns

搜索

    文章目录