姜远平 發表於 2021-5-11 23:46:00

C#事件总线

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>简介</li><li>实现事件总线<ul><li>定义事件基类</li><li>定义事件参数基类</li><li>定义EventBus</li></ul></li><li>使用事件总线<ul><li>事件及事件参数</li><li>定义发布者</li><li>定义订阅者</li><li>实际使用</li></ul></li><li>总结</li><li>参考资料</li></ul></div><p></p>
<h1 id="简介">简介</h1>
<p><strong>事件总线</strong>是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。</p>
<h1 id="实现事件总线">实现事件总线</h1>
<p>EventBus维护一个事件的字典,发布者、订阅者在事件总线中获取事件实例并执行发布、订阅操作,事件实例负责维护、执行事件处理程序。流程如下:<br>
<img src="https://img2020.cnblogs.com/blog/1495663/202105/1495663-20210511230008820-1351658958.png" alt="" loading="lazy"></p>
<h2 id="定义事件基类">定义事件基类</h2>
<p>事件实例需要在事件总线中注册,定义一个基类方便事件总线进行管理,代码如下:</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 事件基类
/// &lt;/summary&gt;
public abstract class EventBase{ }
</code></pre>
<p>事件实例需要管理、执行已经注册的事件处理程序,为了适应不同的事件参数使用泛型参数,不允许此类实例化。代码如下:</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 泛型事件
/// &lt;/summary&gt;
/// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
public abstract class PubSubEvent&lt;T&gt; : EventBase where T : EventArgs
{
    protected static readonly object locker = new object();

    protected readonly List&lt;Action&lt;object, T&gt;&gt; subscriptions = new List&lt;Action&lt;object, T&gt;&gt;();

    public void Subscribe(Action&lt;object, T&gt; eventHandler)
    {
      lock (locker)
      {
            if (!subscriptions.Contains(eventHandler))
            {
                subscriptions.Add(eventHandler);
            }
      }
    }

    public void Unsubscribe(Action&lt;object, T&gt; eventHandler)
    {
      lock (locker)
      {
            if (subscriptions.Contains(eventHandler))
            {
                subscriptions.Remove(eventHandler);
            }
      }
    }

    public virtual void Publish(object sender, T eventArgs)
    {
      lock (locker)
      {
            for (int i = 0; i &lt; subscriptions.Count; i++)
            {
                subscriptions(sender, eventArgs);
            }
      }
    }
}
</code></pre>
<h2 id="定义事件参数基类">定义事件参数基类</h2>
<p>事件参数基类继承EventArgs,使用泛型参数适应不同的参数类型,不允许此类实例化。代码如下:</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 泛型事件参数
/// &lt;/summary&gt;
/// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
public abstract class PubSubEventArgs&lt;T&gt; : EventArgs
{
    public T Value { get; set; }
}
</code></pre>
<h2 id="定义eventbus">定义EventBus</h2>
<p>EventBus只提供事件实例的管理,具体事件处理程序的执行由事件实例自己负责。为了使用方便,构造函数有自动注册事件的功能,在有多个程序集时可能会有bug。代码如下:</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 事件总线
/// &lt;/summary&gt;
class EventBus
{
    private static EventBus _default;
    private static readonly object locker = new object();
    private Dictionary&lt;Type, EventBase&gt; eventDic = new Dictionary&lt;Type, EventBase&gt;();
   
    /// &lt;summary&gt;
    /// 默认事件总线实例,建议只使用此实例
    /// &lt;/summary&gt;
    public static EventBus Default
    {
      get
      {
            if (_default == null)
            {
                lock (locker)
                {
                  // 如果类的实例不存在则创建,否则直接返回
                  if (_default == null)
                  {
                        _default = new EventBus();                           
                  }
                }
            }
            return _default;
      }
    }

    /// &lt;summary&gt;
    /// 构造函数,自动加载EventBase的派生类实现
    /// &lt;/summary&gt;
    public EventBus()
    {
      Type type = typeof(EventBase);
      Type typePubSub = typeof(PubSubEvent&lt;&gt;);
      Assembly assembly = Assembly.GetAssembly(type);
      List&lt;Type&gt; typeList = assembly.GetTypes()
            .Where(t =&gt; t != type &amp;&amp; t != typePubSub &amp;&amp; type.IsAssignableFrom(t))
            .ToList();
      foreach (var item in typeList)
      {
            EventBase eventBase = (EventBase)assembly.CreateInstance(item.FullName);
            eventDic.Add(item, eventBase);
      }
    }
   
    /// &lt;summary&gt;
    /// 获取事件实例
    /// &lt;/summary&gt;
    /// &lt;typeparam name="TEvent"&gt;事件类型&lt;/typeparam&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public TEvent GetEvent&lt;TEvent&gt;() where TEvent : EventBase
    {
      return (TEvent)eventDic;
    }

    /// &lt;summary&gt;
    /// 添加事件类型
    /// &lt;/summary&gt;
    /// &lt;typeparam name="TEvent"&gt;&lt;/typeparam&gt;
    public void AddEvent&lt;TEvent&gt;() where TEvent : EventBase ,new()
    {
      lock (locker)
      {
            Type type = typeof(TEvent);
            if (!eventDic.ContainsKey(type))
            {
                eventDic.Add(type, new TEvent());                  
            }
      }
    }

    /// &lt;summary&gt;
    /// 移除事件类型
    /// &lt;/summary&gt;
    /// &lt;typeparam name="TEvent"&gt;&lt;/typeparam&gt;
    public void RemoveEvent&lt;TEvent&gt;() where TEvent : EventBase, new()
    {
      lock (locker)
      {
            Type type = typeof(TEvent);
            if (eventDic.ContainsKey(type))
            {
                eventDic.Remove(type);
            }
      }
    }
}
</code></pre>
<h1 id="使用事件总线">使用事件总线</h1>
<h2 id="事件及事件参数">事件及事件参数</h2>
<p>使用事件总线前,需要定义好事件及事件参数。在使用时,发布者、订阅者也必须知道事件类型及事件参数类型。代码如下:</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 泛型事件实现-TestAEvent,重写事件的触发逻辑
/// &lt;/summary&gt;
public class TestAEvent: PubSubEvent&lt;TestAEventArgs&gt;
{
    public override void Publish(object sender, TestAEventArgs eventArgs)
    {
      lock (locker)
      {
            for (int i = 0; i &lt; subscriptions.Count; i++)
            {
                var action= subscriptions;
                Task.Run(() =&gt; action(sender, eventArgs));
            }
      }
    }
}
/// &lt;summary&gt;
/// 泛型事件参数实现-TestAEventArgs
/// &lt;/summary&gt;
public class TestAEventArgs : PubSubEventArgs&lt;string&gt; { }


/// &lt;summary&gt;
/// 泛型事件实现-TestBEvent
/// &lt;/summary&gt;
public class TestBEvent : PubSubEvent&lt;TestBEventArgs&gt; { }
/// &lt;summary&gt;
/// 泛型事件参数实现-TestBEventArgs
/// &lt;/summary&gt;
public class TestBEventArgs : PubSubEventArgs&lt;int&gt; { }
</code></pre>
<p><em>注:TestAEvent中重写了事件发布的逻辑,每个事件在任务中执行。</em></p>
<h2 id="定义发布者">定义发布者</h2>
<p>发布者通过事件总线获取事件实例,在实例上发布事件,代码如下:</p>
<pre><code class="language-csharp">class Publisher
{      
    public void PublishTeatAEvent(string value)
    {
      EventBus.Default.GetEvent&lt;TestAEvent&gt;().Publish(this, new TestAEventArgs() { Value=value});
    }

    public void PublishTeatBEvent(int value)
    {
      EventBus.Default.GetEvent&lt;TestBEvent&gt;().Publish(this, new TestBEventArgs() { Value = value });
    }
}
</code></pre>
<h2 id="定义订阅者">定义订阅者</h2>
<p>订阅者通过事件总线获取事件实例,在实例上订阅事件,代码如下:</p>
<pre><code class="language-csharp">class ScbscriberA
{
    public string Name { get; set; }

    public ScbscriberA(string name)
    {
      Name = name;
      EventBus.Default.GetEvent&lt;TestAEvent&gt;().Subscribe(TeatAEventHandler);
    }

    public void TeatAEventHandler(object sender, TestAEventArgs e)
    {
      Console.WriteLine(Name+":"+e.Value);
    }
}

class ScbscriberB
{
    public string Name { get; set; }

    public ScbscriberB(string name)
    {
      Name = name;
      EventBus.Default.GetEvent&lt;TestBEvent&gt;().Subscribe(TeatBEventHandler);
    }

    public void Unsubscribe_TeatBEvent()
    {
      EventBus.Default.GetEvent&lt;TestBEvent&gt;().Unsubscribe(TeatBEventHandler);
    }

    public void TeatBEventHandler(object sender, TestBEventArgs e)
    {
      Console.WriteLine(Name + ":" + e.Value);
    }
}
</code></pre>
<h2 id="实际使用">实际使用</h2>
<p>代码如下:</p>
<pre><code class="language-csharp">class Program
{
    static void Main(string[] args)
    {
      Publisher publisher = new Publisher();
      ScbscriberA scbscriberA = new ScbscriberA("scbscriberA");
      ScbscriberB scbscriberB1 = new ScbscriberB("scbscriberB1");
      ScbscriberB scbscriberB2 = new ScbscriberB("scbscriberB2");
      publisher.PublishTeatAEvent("test");
      publisher.PublishTeatBEvent(123);

      scbscriberB2.Unsubscribe_TeatBEvent();
      publisher.PublishTeatBEvent(12345);

      Console.ReadKey();         
    }   
}
</code></pre>
<p>运行结果:</p>
<pre><code>scbscriberB1:123
scbscriberB2:123
scbscriberA:test
scbscriberB1:12345
</code></pre>
<h1 id="总结">总结</h1>
<p>这个事件总线只提供了基础功能,实现的发布者和订阅者的解耦,发布者、订阅者只依赖事件不互相依赖。<br>
感觉我对事件总线的理解还有点不足,欢迎大家来一起讨论!</p>
<h1 id="参考资料">参考资料</h1>
<p>c# – 使用反射来发现派生类型<br>
Prism.Core/Events/EventBase.cs<br>
C# 事件总线 EventBus</p><br><br>
来源:https://www.cnblogs.com/timefiles/p/CsharpEventBase.html
頁: [1]
查看完整版本: C#事件总线