65.9K
CodeProject 正在变化。 阅读更多。
Home

# C# 中的集中式事件分派器 - 第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (12投票s)

2016 年 2 月 10 日

CPOL

5分钟阅读

viewsIcon

20323

在本系列文章中,我将与您分享我在最近的一个项目中使用的用于集中式事件分发器的解决方案。

引言

在本系列文章中,我将与您分享我在最近的一个项目中使用的用于集中式事件分发器的解决方案。所有源代码都将在我的公共 GitHub 存储库中提供。我试图实现的理念是创建一个中心类,该类可以为任何侦听器触发事件以供订阅。

假设

假定本文的读者已经对 C# 编程有了很好的理解,能够创建项目和解决方案、类和 Windows 窗体,并且能够向项目添加引用并将引用导入类。还假定读者理解基本的继承和接口实现。

解决方案

我创建了一个包含两个项目的解决方案,一个是名为 `Dibware.EventDispatcher.UI` 的 Windows 窗体应用程序项目,另一个是名为 `Dibware.EventDispatcher.Core` 的类库。我将把所有的事件分发器代码都放在类库中,这样如果您喜欢这个解决方案,您就可以直接获取 DLL 并将其用于您自己的项目中,而不会受到消耗代码的干扰。

Dibware.EventDispatcher.Core

首先,我想在类库中定义一个所有事件对象都将遵循的契约。我将它命名为 `IApplicationEvent`,它将是一个名为 `Contracts` 的文件夹中的空 `public` 接口。

public interface IApplicationEvent{ }

任何想要处理分发器将发布的任何事件的方法,都需要符合预定义的 método 签名。这将由 `Contracts` 文件夹中的 `ApplicationEventHandlerDelegate` 委托定义。

public delegate void ApplicationEventHandlerDelegate<in TEvent>(TEvent @event) 
	where TEvent : IApplicationEvent;

此委托有一个泛型类型参数 `TEvent`,它可以是逆变的,并且将接受一个泛型类型的单个参数(该参数将是事件对象),但事件对象的类型将被约束为实现 `IApplicationEvent` 接口。

在同一个文件夹中,我将创建一个 `public` 接口契约,事件分发器将遵循该契约,名为 `IApplicationEventDispatcher`。此接口将包含三个成员:一个用于向分发器添加侦听器,一个用于从分发器移除侦听器,以及一个用于分发事件。侦听器必须符合 `ApplicationEventHandlerDelegate` 定义的签名。 `Dispatch` 方法将接受一个符合 `IApplicationEvent` 接口的参数。 `IApplicationEventDispatcher` 还将要求实现 `IDisposable`,以便任何资源都可以得到妥善清理。

    public interface IApplicationEventDispatcher : IDisposable
    {
        void AddListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler) 
        	where TEvent : IApplicationEvent;
        void RemoveListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler) 
        	where TEvent : IApplicationEvent;
        void Dispatch<TEvent>(TEvent @event) where TEvent : IApplicationEvent;
    }

现在我们可以创建 `public` 的分发器类本身了,我们将把它放在类库程序集的根目录下。我们将它命名为 `ApplicationEventDispatcher`,它将实现 `IApplicationEventDispatcher` 接口,并继承 `IDisposable`。

public class ApplicationEventDispatcher : IApplicationEventDispatcher
 {
     public void Dispose()
     {
         throw new System.NotImplementedException();
     }

     public void AddListener<TEvent>
     (ApplicationEventHandlerDelegate<TEvent> handler) where TEvent : IApplicationEvent
     {
         throw new System.NotImplementedException();
     }

     public void RemoveListener<TEvent>
     (ApplicationEventHandlerDelegate<TEvent> handler) where TEvent : IApplicationEvent
     {
         throw new System.NotImplementedException();
     }

     public void Dispatch<TEvent>(TEvent @event) where TEvent : IApplicationEvent
     {
         throw new System.NotImplementedException();
     }
 }

因此,让我们正确地在类中实现 `Dispose` 模式。

private bool _disposed;

 ~ApplicationEventDispatcher()
 {
     Dispose(false);
 }

 public void Dispose()
 {
     Dispose(true);
     GC.SuppressFinalize(this);
 }

 private void Dispose(bool disposing)
 {
     if (_disposed) return;

     if (disposing)
     {
         // free other managed objects that implement IDisposable only
     }

     // release any unmanaged objects
     // set the object references to null

     _disposed = true;
 }

下一个任务是为事件处理程序创建一个后端存储。为此,我们将使用一个字典,其中键是需要处理的事件类型,值是处理该事件的委托。此字典将在类构造函数中初始化。稍后我们还需要妥善处理它内部的任何委托,但现在我们只在 `Dispose(bool)` 方法中将引用设置为 `null`,就在设置 `_disposed = true;` 之前。

private Dictionary<Type, Delegate> _applicationEventHandlers;

 public ApplicationEventDispatcher()
 {
     _applicationEventHandlers = new Dictionary<Type, Delegate>();
 }

 private void Dispose(bool disposing)
 {
     if (_disposed) return;

     if (disposing)
     {
         // free other managed objects that implement IDisposable only
     }

     // release any unmanaged objects
     // set the object references to null

     _applicationEventHandlers = null;

     _disposed = true;
 }

现在我们可以专注于通过在空的 `AddListener` 方法中添加实现来向分发器添加侦听器。第一项任务是查看我们的字典存储是否已经有任何类型的事件处理程序的委托。如果有,我们就获取对委托的引用并将新委托与它们合并。如果没有,我们就使用事件类型作为键将处理程序添加到字典中。

public void AddListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler)
     where TEvent : IApplicationEvent
 {
     Delegate @delegate;
     if (_applicationEventHandlers.TryGetValue(typeof(TEvent), out @delegate))
     {
         _applicationEventHandlers[typeof(TEvent)] = Delegate.Combine(@delegate, handler);
     }
     else
     {
         _applicationEventHandlers[typeof(TEvent)] = handler;
     }
 }

如果“有什么上去就必须下来”,那么“被添加的”就必须有机会被“移除”。我们可以通过在空的 `RemoveListener` 方法中添加实现来实现这一点。同样,第一项任务是查看我们的字典中是否有该事件类型的事件。如果有,我们就可以查找我们的处理程序是否在委托列表中,如果在,就将其从委托调用列表中移除。如果调用列表中不再有委托,则完全移除字典条目。

public void RemoveListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler)
     where TEvent : IApplicationEvent
 {
     Delegate @delegate;
     if (_applicationEventHandlers.TryGetValue(typeof(TEvent), out @delegate))
     {
         Delegate currentDel = Delegate.Remove(@delegate, handler);

         if (currentDel == null)
         {
             _applicationEventHandlers.Remove(typeof(TEvent));
         }
         else
         {
             _applicationEventHandlers[typeof(TEvent)] = currentDel;
         }
     }
 }

现在我们有了添加和移除处理程序到分发器的方法,让我们提供一些功能来将事件分发给任何已订阅的侦听器。如果传入的事件是 `null`,则立即抛出异常,否则使用事件类型在字典中查找该事件的处理程序,如果存在处理程序,则调用它们!

public void Dispatch<TEvent>(TEvent @event) where TEvent : IApplicationEvent
 {
     if (@event == null)
     {
         throw new ArgumentNullException("event");
     }

     Delegate @delegate;
     if (_applicationEventHandlers.TryGetValue(typeof(TEvent), out @delegate))
     {
         ApplicationEventHandlerDelegate<TEvent> callback = 
         	@delegate as ApplicationEventHandlerDelegate<TEvent>;
         if (callback != null)
         {
             callback(@event);
         }
     }
 }

理论上,所有订阅事件分发器事件的代码都会取消订阅自己的处理程序,但考虑到订阅代码可能没有执行此任务,我们需要确保在处置此事件分发器时断开所有处理程序的连接。为此,我们将添加一个 `RemoveAllListeners` 方法,该方法将在 `Dispose(bool)` 的 `disposing` 代码路径中调用。

  private void Dispose(bool disposing)
 {
     if (_disposed) return;

     if (disposing)
     {
         // free other managed objects that implement IDisposable only
     }

     // release any unmanaged objects
     // set the object references to null
     RemoveAllListeners();

     _applicationEventHandlers = null;

     _disposed = true;
 }

此方法将收集所有处理程序类型,迭代它们并取消订阅调用列表中的所有委托,最后,当处理程序类型不再有任何委托时,移除 `dictionary` 条目。

private void RemoveAllListeners()
 {
     var handlerTypes = new Type[_applicationEventHandlers.Keys.Count];
     _applicationEventHandlers.Keys.CopyTo(handlerTypes, 0);

     foreach (Type handlerType in handlerTypes)
     {
         Delegate[] delegates = _applicationEventHandlers[handlerType].GetInvocationList();
         foreach (Delegate @delegate1 in delegates)
         {
             var handlerToRemove = Delegate.Remove(_applicationEventHandlers[handlerType], @delegate1);
             if (handlerToRemove == null)
             {
                 _applicationEventHandlers.Remove(handlerType);
             }
             else
             {
                 _applicationEventHandlers[handlerType] = handlerToRemove;
             }
         }
     }
 }

第一部分到此结束,我们已经创建了 `ApplicationEventDispatcher`。在第二部分,我们将研究如何在 Windows 窗体应用程序中实现它。

完整的代码可在 我的 EventDispatcher GitHub 存储库 获取。

© . All rights reserved.