多事件类型的单事件总线

时间:2012-03-06 14:02:01

标签: c# asp.net oop design-patterns

我设置了一个事件总线,只传递所有事件类型的字符串。这很好但现在我想为每种事件类型设置不同的事件参数。我没有看到一种方法来保持所有具有不同事件参数的订阅者集合。我可以为事件参数使用基类型,但是事件处理程序被强制使用基类型,订阅者必须将事件参数强制转换为具体类型(我不想要)。我基本上有类似的东西:

public abstract class PresentationEvent

{
    private readonly List<Action<IPresentationEventArgs>> _subscribers = new List<Action<IPresentationEventArgs>>();

    public void Subscribe(Action<IPresentationEventArgs> action)
    {
        _subscribers.Add(action);
    }

    public void Publish(IPresentationEventArgs message)
    {
        foreach (var sub in _subscribers)
        {
            sub.Invoke(message);
        }
    }
}

 public class MessageChangedEvent : PresentationEvent
    {

    }

public static class EventBus 
    {
        private static readonly Dictionary<Type, PresentationEvent> _mapping = new Dictionary<Type, PresentationEvent>();

        private static PresentationEvent GetPresentationEvent<T>() where T : PresentationEvent, new()
        {
            if (_mapping.ContainsKey(typeof(T)))
            {
                return _mapping[typeof(T)];
            }

            var presEvent = new T();
            _mapping.Add(typeof(T), presEvent);

            return presEvent;
        }

        public static void Subscribe<T>(Action<IPresentationEventArgs> action) where T: PresentationEvent, new()
        {
            var presEvent = GetPresentationEvent<T>();
            presEvent.Subscribe(action);
        }

        public static void Publish<T>(IPresentationEventArgs args) where T : PresentationEvent, new()
        {
            var presEvent = GetPresentationEvent<T>();
            presEvent.Publish(args);
        }
    }

但是在处理这个事件时,我不得不这样做:

private void OnMessageChanged(IPresentationEventArgs x)
        {
// do cast here
        }

而不是:

 private void OnMessageChanged(MessageChangedEventArgs args)
        {
            label1.Text = args.Message;
        }

除了为每个事件类型保留一些具有不同列表的事件字典之外,我不知道如何处理这个问题。我知道那里有第三方库,但我更喜欢自己编写代码。我也看了类似的问题,但没有找到任何东西。如果有人建议如何解决这个问题或其他建议,将不胜感激。

3 个答案:

答案 0 :(得分:1)

使用通用接口可以做一些有趣的事情,这是代理人无法做到的。一种可能在这里工作的方法,如果每个使用“事件”的类只需要为每种类型的参数设置一个处理程序,那么就是使用方法IKingEventHandler<T>定义一个接口InvokeEvent(T param),并且有一个方法RaiseKingEvent<TT>(TT param),它搜索订阅的处理程序对象列表并调用任何实现IKingEventHandler<TT>的方法。如果不想为每种类型的处理程序定义单独的参数类型,除了参数类型之外,还可以包括伪类型参数。这种方法会在某种程度上限制可以处理的事件模式,但它会比普通代表具有一些优势:

  1. 无论好坏,将对象放入订阅者列表会自动附加其所有相关事件。
  2. 订阅者列表可以将每个订阅者保持为“WeakReference”,从而避免传统上困扰事件发布者的内存泄漏问题。

这不是接口可以做的最有趣的事情,委托不能(接口支持开放泛型方法的能力更有趣)但在某些情况下它可能仍然是一个有用的模式。

答案 1 :(得分:1)

我使用类似的东西来提升域名事件。这是基本的想法(更改了代码,因此未经测试):

public static class EventBus
{
    private static List<Delegate> actions;

    public static void Register<T>(Action<T> callback) where T : IPresentationEvent
    {
        if (actions == null)
        {
            actions = new List<Delegate>();
        }

        actions.Add(callback);
    }

    public static void ClearCallbacks()
    {
        actions = null;
    }

    public static void Raise<T>(T args) where T : IPresentationEvent
    {
        if (actions == null)
        {
           return;
        }

        foreach (var action in actions)
        {
            if (!(action is Action<T>))
            {
                continue;
            }

            ((Action<T>)action).Invoke(args);
        }
    }
}

<强>更新

我有一个标记界面:

public interface IPresentationEvent
{
}

处理程序看起来像这样:

public interface IHandlePresentationEvent<T> where T : IPresentationEvent
{
    void Handle(T args);
}

答案 2 :(得分:1)

如果添加另一个通用参数,则可以使用强类型事件。

public interface IPresentationEventArgs { }

public abstract class PresentationEvent<TPresentationEventArgs> where TPresentationEventArgs : IPresentationEventArgs
{
    private readonly List<Action<TPresentationEventArgs>> _subscribers = new List<Action<TPresentationEventArgs>>();

    public void Subscribe(Action<TPresentationEventArgs> action)
    {
        _subscribers.Add(action);
    }

    public void Publish(TPresentationEventArgs message)
    {
        foreach (var sub in _subscribers)
        {
            sub.Invoke(message);
        }
    }
}

public class MessageChangedEventArgs : IPresentationEventArgs 
{
    public string Message { get; set; }
}

public class MessageChangedEvent : PresentationEvent<MessageChangedEventArgs>
{

}

public static class EventBus
{
    private static readonly Dictionary<Type, Func<Object>> _mapping = new Dictionary<Type, Func<Object>>();

    private static T GetPresentationEvent<T, TArgs>()
        where T : PresentationEvent<TArgs>, new()
        where TArgs : IPresentationEventArgs
    {
        if (_mapping.ContainsKey(typeof(T)))
        {
            return _mapping[typeof(T)]() as T;
        }

        var presEvent = new T();
        _mapping.Add(typeof(T), () => presEvent);

        return presEvent;
    }

    public static void Subscribe<T, TArgs>(Action<TArgs> action) where T : PresentationEvent<TArgs>, new()
        where TArgs : IPresentationEventArgs
    {
        var presEvent = GetPresentationEvent<T, TArgs>();
        presEvent.Subscribe(action);
    }

    public static void Publish<T, TArgs>(TArgs args) where T : PresentationEvent<TArgs>, new()
        where TArgs : IPresentationEventArgs
    {
        var presEvent = GetPresentationEvent<T, TArgs>();
        presEvent.Publish(args);
    }
}

这是一个小型测试程序,用于演示如何使用它:

class Program
{
    static void OnMessageChanged(MessageChangedEventArgs args)
    {
        Console.WriteLine(args.Message);
    }

    static void Main(string[] args)
    {
        EventBus.Subscribe<MessageChangedEvent, MessageChangedEventArgs>(OnMessageChanged);
        EventBus.Publish<MessageChangedEvent, MessageChangedEventArgs>(new MessageChangedEventArgs{ Message = "Hello world."});

        Console.ReadKey();
    }
}

您有使用2个通用参数调用subscribe和publish的额外开销,但另一方面,您可以将事件绑定到特定的eventArgs,并且消费者无法为给定事件传递任何任意的eventArgs。他们需要匹配。

这是一个小优化。您可以只添加操作,并允许多播委托的强大功能为您跟踪所有操作,而不是创建自己的操作列表。例如:

public abstract class PresentationEvent<TPresentationEventArgs> where TPresentationEventArgs : IPresentationEventArgs
{
    private Action<TPresentationEventArgs> _actions = args => { };

    public void Subscribe(Action<TPresentationEventArgs> action)
    {
        _actions += action;
    }

    public void Publish(TPresentationEventArgs message)
    {
        _actions(message);
    }
}

<强>更新

这是您可以进行订阅的另一种方式。但无论你采用哪种方法,如果你想要静态链接和编译时间检查,那么你需要提供2种类型的参数。

  • 1个类型参数,用于指定要订阅的事件类型。
  • 1个类型参数,用于将订阅的方法强制转换为Action,因为编译器无法仅根据方法签名推断出它的情况

考虑到这一点,这是另一种方式,但你不必避免必须指定2个参数。

public static class IPresentationEventArgsExtensions
{
    public static void SubscribeTo<TEvent, TArgs>(this TEvent target, Action<TArgs> action)
        where TArgs : IPresentationEventArgs
        where TEvent : PresentationEvent<TArgs>, new()
    {
        EventBus.Subscribe<TEvent, TArgs>(action);
    }
}

// Use
 Action<MessageChangedEventArgs> messageChangedMethod = OnMessageChanged; // The compiler cannot infer that OnMessageChanged is a Action<IPresentationEventArgs>
 new MessageChangedEvent().SubscribeTo(messageChangedMethod);