C# 基于消息发布订阅模型的示例(上)

 更新时间:2021年03月01日 08:53:29   作者:Hello——寻梦者!  
这篇文章主要介绍了C# 基于消息发布订阅模型的示例,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下

  在我们的开发过程中,我们经常会遇到这样的场景就是一个对象的其中的一些状态依赖于另外的一个对象的状态,而且这两个对象之间彼此是没有关联的,及两者之间的耦合性非常低,特别是在这种基于容器模型的开发中遇到的会非常多,比如Prism框架或者MEF这种框架中,而我们会发现在这样的系统中我们经常使用一种Publish和Subscribe的模式来进行交互,这种交互有什么好处呢?基于带着这些问题的思考,我们来一步步来剖析!

  首先第一步就是定义一个叫做IEventAggregator的接口,里面定义了一些重载的Subscribe和Publish方法,我们具体来看一看这个接口:

/// <summary>
   ///   Enables loosely-coupled publication of and subscription to events.
   /// </summary>
   public interface IEventAggregator
   {
       /// <summary>
       ///   Gets or sets the default publication thread marshaller.
       /// </summary>
       /// <value>
       ///   The default publication thread marshaller.
       /// </value>
       Action<System.Action> PublicationThreadMarshaller { get; set; }
 
       /// <summary>
       ///   Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
       /// </summary>
       /// <param name = "instance">The instance to subscribe for event publication.</param>
       void Subscribe(object instance);
 
       /// <summary>
       ///   Unsubscribes the instance from all events.
       /// </summary>
       /// <param name = "instance">The instance to unsubscribe.</param>
       void Unsubscribe(object instance);
 
       /// <summary>
       ///   Publishes a message.
       /// </summary>
       /// <param name = "message">The message instance.</param>
       /// <remarks>
       ///   Uses the default thread marshaller during publication.
       /// </remarks>
       void Publish(object message);
 
       /// <summary>
       ///   Publishes a message.
       /// </summary>
       /// <param name = "message">The message instance.</param>
       /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
       void Publish(object message, Action<System.Action> marshal);
   }

  有了这个接口,接下来就是怎样去实现这个接口中的各种方法,我们来看看具体的实现过程。

/// <summary>
    ///   Enables loosely-coupled publication of and subscription to events.
    /// </summary>
    public class EventAggregator : IEventAggregator
    {
        /// <summary>
        ///   The default thread marshaller used for publication;
        /// </summary>
        public static Action<System.Action> DefaultPublicationThreadMarshaller = action => action();
 
        readonly List<Handler> handlers = new List<Handler>();
 
        /// <summary>
        ///   Initializes a new instance of the <see cref = "EventAggregator" /> class.
        /// </summary>
        public EventAggregator()
        {
            PublicationThreadMarshaller = DefaultPublicationThreadMarshaller;
        }
 
        /// <summary>
        ///   Gets or sets the default publication thread marshaller.
        /// </summary>
        /// <value>
        ///   The default publication thread marshaller.
        /// </value>
        public Action<System.Action> PublicationThreadMarshaller { get; set; }
 
        /// <summary>
        ///   Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
        /// </summary>
        /// <param name = "instance">The instance to subscribe for event publication.</param>
        public virtual void Subscribe(object instance)
        {
            lock(handlers)
            {
                if (handlers.Any(x => x.Matches(instance)))
                {
                    return;
                }                   
                handlers.Add(new Handler(instance));
            }
        }
 
        /// <summary>
        ///   Unsubscribes the instance from all events.
        /// </summary>
        /// <param name = "instance">The instance to unsubscribe.</param>
        public virtual void Unsubscribe(object instance)
        {
            lock(handlers)
            {
                var found = handlers.FirstOrDefault(x => x.Matches(instance));
                if (found != null)
                {
                   handlers.Remove(found);
                }                  
            }
        }
 
        /// <summary>
        ///   Publishes a message.
        /// </summary>
        /// <param name = "message">The message instance.</param>
        /// <remarks>
        ///   Does not marshall the the publication to any special thread by default.
        /// </remarks>
        public virtual void Publish(object message)
        {
            Publish(message, PublicationThreadMarshaller);
        }
 
        /// <summary>
        ///   Publishes a message.
        /// </summary>
        /// <param name = "message">The message instance.</param>
        /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
        public virtual void Publish(object message, Action<System.Action> marshal)
        {
            Handler[] toNotify;
            lock (handlers)
            {
                toNotify = handlers.ToArray();
            }
            marshal(() =>
            {
                var messageType = message.GetType();
                var dead = toNotify
                    .Where(handler => !handler.Handle(messageType, message))
                    .ToList();
 
                if(dead.Any())
                {
                    lock(handlers)
                    {
                        foreach(var handler in dead)
                        {
                            handlers.Remove(handler);
                        }
                    }
                }
            });
        }
 
        protected class Handler
        {
            readonly WeakReference reference;
            readonly Dictionary<Type, MethodInfo> supportedHandlers = new Dictionary<Type, MethodInfo>();
 
            public Handler(object handler)
            {
                reference = new WeakReference(handler);
 
                var interfaces = handler.GetType().GetInterfaces()
                    .Where(x => typeof(IHandle).IsAssignableFrom(x) && x.IsGenericType);
 
                foreach(var @interface in interfaces)
                {
                    var type = @interface.GetGenericArguments()[0];
                    var method = @interface.GetMethod("Handle");
                    supportedHandlers[type] = method;
                }
            }
 
            public bool Matches(object instance)
            {
                return reference.Target == instance;
            }
 
            public bool Handle(Type messageType, object message)
            {
                var target = reference.Target;
                if(target == null)
                    return false;
 
                foreach(var pair in supportedHandlers)
                {
                    if(pair.Key.IsAssignableFrom(messageType))
                    {
                        pair.Value.Invoke(target, new[] { message });
                        return true;
                    }
                }
                return true;
            }
        }
    }

  首先在EventAggregator的内部维护了一个LIst<Handler>的List对象,用来存放一系列的Handle,那么这个嵌套类Handler到底起什么作用呢?

  我们会发现在每一次当执行这个Subscribe的方法的时候,会将当前object类型的参数instance传入到Handler这个对象中,在Handler这个类的构造函数中,首先将这个instance放入到一个弱引用中去,然后再获取这个对象所有继承的接口,并查看是否继承了IHandle<TMessage>这个泛型的接口,如果能够获取到,那么就通过反射获取到当前instance中定义的Handle方法,并获取到其中定义的表示泛型类型的类型实参或泛型类型定义的类型形参,并把这两个对象放到内部定义的一个Dictionary<Type, MethodInfo>字典之中,这样就把这样一个活得具体的处理方法的Handler对象放到了一个List<Handler>集合中,这个就是订阅消息的核心部分,所以当前的对象要想订阅一个消息,那么必须实现泛型接口IHandle<TMessage>,并且实现接口中的方法,同时最重要的就是在当前对象的构造函数函数中去订阅消息(即执行Subscribe(this),我们来看一看这个泛型接口IHandle<TMessage> 

public interface IHandle {}
 
/// <summary>
///   Denotes a class which can handle a particular type of message.
/// </summary>
/// <typeparam name = "TMessage">The type of message to handle.</typeparam>
public interface IHandle<TMessage> : IHandle
{
    /// <summary>
    ///   Handles the message.
    /// </summary>
    /// <param name = "message">The message.</param>
    void Handle(TMessage message);
}

  在看完了Subscribe这个方法后,后面我们就来看看Unsubscribe方法吧,这个思路其实很简单就是找到List<Handler>中的这个对象,并且移除当前的对象就可以了,那么下面我们关注的重点就是Publish这个方法中到底实现了什么?首先来看看代码,然后再来做一步步分析。 

/// <summary>
        ///   Publishes a message.
        /// </summary>
        /// <param name = "message">The message instance.</param>
        /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
        public virtual void Publish(object message, Action<System.Action> marshal)
        {
            Handler[] toNotify;
            lock (handlers)
            {
                toNotify = handlers.ToArray();
            }
            marshal(() =>
            {
                var messageType = message.GetType();
                var dead = toNotify
                    .Where(handler => !handler.Handle(messageType, message))
                    .ToList();
 
                if(dead.Any())
                {
                    lock(handlers)
                    {
                        foreach(var handler in dead)
                        {
                            handlers.Remove(handler);
                        }
                    }
                }
            });
        }

  我们看到,在发布一个object类型的message的时候,必然对应着另外的一个对象来处理这个消息,那么怎样找到这个消息的处理这呢?

  对,我们在Subscribe一个对象的时候不是已经通过反射将订阅这个消息的对象及方法都存在了一个List<Handler>中去了吗?那么我们只需要在这个List中找到对应的和message类型一致的那个对象并执行里面的Handle方法不就可以了吗?确实是一个很好的思路,这里我们看代码也是这样实行的。

  这里面还有一个要点就是,如果执行的方法返回了false,就是执行不成功,那么就从当前的List<Handler>中移除掉这个对象,因为这样的操作是没有任何意义的,通过这样的过程我们就能够完没地去实现两个对象之间的消息传递了,另外我们通过总结以后就能够发现,这个思路实现的重点包括以下方面:

  1 所有消息订阅的对象必须实现统一的接口IHandle<TMessage>,并实现里面的Handel方法。

  2 整个EventAggregator必须是单实例或者是静态的,这样才能够在统一的集合中去实现上述的各种操作。

  最后还是按照之前的惯例,最后给出一个具体的实例来做相关的说明,请点击此处进行下载,在下篇中我们将介绍一种简单版的基于事件的发布和订阅模式的例子。

以上就是C# 基于消息发布订阅模型的示例(上)的详细内容,更多关于c# 发布订阅模型的资料请关注脚本之家其它相关文章!

相关文章

  • Unity实现虚拟键盘

    Unity实现虚拟键盘

    这篇文章主要为大家详细介绍了Unity实现虚拟键盘,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • C#灰度化图像的实例代码

    C#灰度化图像的实例代码

    灰度化一幅图像就是将图像的色彩信息全部丢掉,将24位的位图信息,用8位来表示,灰度图共有256级灰度等级,也就是将24位位图的一点如(255,255,255)转换成255,所以R,G,B三个值所乘的系数和为1
    2013-09-09
  • C#数据结构之双向链表(DbLinkList)实例详解

    C#数据结构之双向链表(DbLinkList)实例详解

    这篇文章主要介绍了C#数据结构之双向链表(DbLinkList),结合实例形式较为详细的讲解了双向链表的概念及C#实现双向链表的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-11-11
  • winform实现拖动文件到窗体上的方法

    winform实现拖动文件到窗体上的方法

    这篇文章主要介绍了winform实现拖动文件到窗体上的方法,以实例分析了C#中WinForm操作窗体及文件的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-09-09
  • C#使用TcpListener及TcpClient开发一个简单的Chat工具实例

    C#使用TcpListener及TcpClient开发一个简单的Chat工具实例

    下面小编就为大家分享一篇C#使用TcpListener及TcpClient开发一个简单的Chat工具实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • C#中单问号(?)和双问号(??)的用法整理

    C#中单问号(?)和双问号(??)的用法整理

    本文详细讲解了C#中单问号(?)和双问号(??)的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • C#编程总结(一)序列化总结

    C#编程总结(一)序列化总结

    本篇主要介绍了C#序列化总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12
  • c#:CTS类型系统

    c#:CTS类型系统

    CTS通用类型系统,是.Net中一套定义类型的规则。我们要掌握c#开发,首先要建立这个类型概念,只有知道c#的元素是什么类型,才能进行相关的分析和选材。
    2012-12-12
  • 高效C#编码优化原则

    高效C#编码优化原则

    这篇文章主要介绍了高效C#编码优化原则,非常实用,需要的朋友可以参考下
    2014-08-08
  • C#如何对Dictionary遍历赋值

    C#如何对Dictionary遍历赋值

    这篇文章主要介绍了C#如何对Dictionary遍历赋值问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09

最新评论