聚合事件源和重新调度事件的最佳方式

本文关键字:调度 事件 方式 最佳 事件源 新调度 | 更新日期: 2023-09-27 18:35:52

我正在尝试找到创建系统的最佳方法,在该系统中,可以将事件源添加到管理器类中,然后管理器类将其事件重新调度给侦听器。 具体来说,我有许多不同的输入源(键盘输入源、鼠标输入源、虚拟键盘输入源等),我想允许开发人员侦听键盘输入源和输入管理器本身上的 KeyDown 事件(从任何活动输入源捕获此事件)。

暴力破解

一个解决方案很容易,我最终会创建许多"调度"函数,这些函数只是在事件通过时重新调度事件,但我最终有几十个单行函数,每当将新事件添加到输入源接口时,我都必须创建新函数。

我已经考虑过使用 lambda,但如果从管理器中删除输入源,我需要一种方法来解除事件钩。 我可以将 lambda 保存在字典中,由输入源键控,但许多事件都有不同的 arg 类,并且创建多个字典来执行此操作开始变得丑陋。

我想知道我是否缺少一些简单的方法来保持事情干净并保持我需要写下的额外代码量。

作为参考,下面是我正在使用的对象的示例:

public interface IInputSource {}
public interface IKeyboardInputSource : IInputSource
{
    event EventHandler<KeyboardEventArgs> KeyDown;
    event EventHandler<KeyboardEventArgs> KeyUp;
}
public interface IMouseInputSource : IInputSource
{
    event EventHandler<MouseEventArgs> MouseDown;
    event EventHandler<MouseEventArgs> MouseUp;
}
public class InputManager : IKeyboardInputSource, IMouseInputSource
{
    private List<IInputSource> InputSources;
    //Event declarations from IKeyboardInputSource and IMouseInputSource
    public void AddSource(IInputSource source)
    {
        InputSources.Add(source);
        if (source is IKeyboardInputSource)
        {
            var keyboardSource = source as IKeyboardInputSource;
            keyboardSource.KeyDown += SendKeyDown;
            // Listen for other keyboard events...
        }
        if (source is IMouseInputSource)
        {
            // Listen for mouse events...
        }
    }
    public void RemoveSource(IInputSource source)
    {
        if (source is IKeyboardInputSource)
        {
            var keyboardSource = source as IKeyboardInputSource;
            keyboardSource.KeyDown -= SendKeyDown;
            // Remove other keyboard events...
        }
        if (source is IMouseInputSource)
        {
            // Remove mouse events...
        }
        InputSources.Remove(source);
    }
    private void SendKeyDown(object sender, KeyboardEventArgs e)
    {
        if (KeyDown != null) 
            KeyDown(sender, e);
    }
    //Other "send" functions
}

聚合事件源和重新调度事件的最佳方式

你看过反应式扩展(Rx)框架吗?看起来它会满足您的要求,并为您提供丰富的函数/lambda 如 api 来管理和处理事件。

反应式扩展 (Rx) 是一个库,用于使用可观察序列和 LINQ 样式的查询运算符编写异步和基于事件的程序

可能这样的东西会有所帮助 - 这是一种通用方法,可以直接订阅事件或通过"接收器"接口

interface IInputSource<T> where T : EventArgs
{
    event EventHandler<T> InputEvent;
}
interface IInputSink<in T> where T : EventArgs
{
    void InputMessageHandler(object sender, T eventArgs);
}
internal class InputManager
{
    private Dictionary<Type, object> _inputSources;
    private Dictionary<Type, object> _inputSinks;
    private Dictionary<Type, object> _events;
    public void AddSource<T>(IInputSource<T> source) where T : EventArgs
    {
        _inputSources[typeof(T)] = _inputSources;      //add source
        _events[typeof(T)] = (EventHandler<T>)Dispatch; //register event for subscribers
        source.InputEvent += Dispatch;
        source.InputEvent += Dispatch2;
    }

    // Dispatch trough direct event subscriptions;
    private void Dispatch<T>(object sender, T e) where T : EventArgs
    {
        var handler = _events[typeof(T)] as EventHandler<T>;
        handler.Invoke(sender, e);
    }
    // Dispatch trough IInputSink subscriptions;
    private void Dispatch2<T>(object sender, T e) where T : EventArgs
    {
        var sink = _inputSinks[typeof(T)] as IInputSink<T>;
        sink.InputMessageHandler(sender, e);
    }
    //Subscription:  Client should provide handler into Subscribe()
    //or subscribe with IInputSink<MyEvent> implementation (Subscribe2())
    public void Subscribe<T>(EventHandler<T> handler) where T : EventArgs
    {
        var @event = _events[typeof(T)] as EventHandler<T>;
        _events[typeof(T)] = @event + handler;
    }
    public void Subscribe2<T>(IInputSink<T> sink) where T : EventArgs
    {
        _inputSinks[typeof(T)] = sink;
    }
}
class XXXX : EventArgs
{
}
public class Sink: IInputSink<XXXX>
{
    #region Implementation of IInputSink<in XXXX>
    public void InputMessageHandler(object sender, XXXX eventArgs)
    {
        throw new NotImplementedException();
    }
    #endregion
    public Sink() 
    {
        var v = new InputManager();
        v.Subscribe<XXXX>(GetInputEvent);
        v.Subscribe2(this);
    }
    private void GetInputEvent(object sender, XXXX xxxx)
    {
        throw new NotImplementedException();
    }
}