c# - 适配包含 ref 参数的 C# 事件

标签 c# oop events adapter abstraction

我处于这样一种情况,我必须使用包含很多事件的第 3 方库,恕我直言,写得不是很好。它触发了我必须在我的代码中处理的事件,但我试图将它抽象出来(以便能够对依赖于该库的其余代码进行单元测试)所以我需要一个适配器。问题是有些事件是采用 ref 的委托(delegate)类型参数。这是第 3 方库的示例:

delegate void AdapteeEventHandler1(SpecificAdaptee sender, int a, int b);
delegate void AdapteeEventHandler2(SpecificAdaptee sender, ref int a); // problematic delegate

class SpecificAdaptee
{
    public event AdapteeEventHandler1 Event1;
    public event AdapteeEventHandler2 Event2; // problematic event

    /// <summary>Exercise Event1</summary>
    public void FireEvent1()
    {
        Event1?.Invoke(this, 1, 2);
    }
    /// <summary>Exercise Event2</summary>
    public void FireEvent2()
    {
        int a = 42;
        Event2?.Invoke(this, ref a);
    }
}

为了展示我是如何提取常规事件的参数列表,它包含 Event1类型 AdapteeEventHandler1 .有问题的类型是 AdapteeEventHandler2 ,但让我先展示一下我将如何调整整个事情:

#region AdaptedEventArgs
class AdaptedEventArgs1 : EventArgs
{
    public int A { get; set; }
    public int B { get; set; }
}

class AdaptedEventArgs2 : EventArgs
{
    public int A { get; set; }
}
#endregion

/// <summary>These represent an abstraction layer between SpecificAdaptee and our own code</summary>
class Adaptor
{
    private readonly SpecificAdaptee _specificAdaptee;
    /// <summary>Maintains relationship between the event triggered by SpecificAdaptee and the adapted event.</summary>
    private readonly IAdaptedEventHandlerManager _adaptedEventHandlerManager;

    public Adaptor(SpecificAdaptee specificAdaptee, IAdaptedEventHandlerManager adaptedEventHandlerManager)
    {
        _specificAdaptee = specificAdaptee;
        _adaptedEventHandlerManager = adaptedEventHandlerManager;
    }

    #region Events
    /// <summary>Adapts SpecificAdaptee.Event1</summary>
    public event EventHandler<AdaptedEventArgs1> AdaptedEvent1
    {
        add
        {
            _specificAdaptee.Event1 += _adaptedEventHandlerManager.RegisterEventHandler<AdapteeEventHandler1>(value,
                (sender, a, b) => value.Invoke(this, new AdaptedEventArgs1 { A = a, B = b }));
        }
        remove
        {
            _specificAdaptee.Event1 -= _adaptedEventHandlerManager.UnregisterEventHandler<AdapteeEventHandler1>(value);
        }
    }

    /// <summary>Adapts SpecificAdaptee.Event2</summary>
    public event EventHandler<AdaptedEventArgs2> AdaptedEvent2
    {
        add
        {
            /* !!! ERROR HERE !!! */
            _specificAdaptee.Event2 += _adaptedEventHandlerManager.RegisterEventHandler<AdapteeEventHandler2>(value,
                (sender, a) => value.Invoke(this, new AdaptedEventArgs2 { A = a }));
        }
        remove
        {
            _specificAdaptee.Event2 -= _adaptedEventHandlerManager.UnregisterEventHandler<AdapteeEventHandler2>(value);
        }
    }
    #endregion
}

所以这里发生的事情是,当我向 Adaptor.AdaptedEvent1 注册一个事件处理程序时我在包装 EventHandler<AdaptedEventArgs1>AdapteeEventHandler1并注册到SpecificAdaptee.Event1 , 也转换 AdaptedEventArgs1 AdapteeEventHandler1 所需的参数列表.这样用户可以注册到 Adaptor 的事件当 SpecificAdaptee 时将被解雇触发自己的事件。接下来我将发布一个程序来练习这个,但请注意问题出在 AdaptedEvent2 中。 ,我想以类似的方式做事,但我不知道如何处理 ref参数(addAdaptedEvent2 访问器中存在语法错误。 这是一个运行该项目的控制台应用程序:

class Program
{
    public static void Main(string[] args)
    {
        var specific = new SpecificAdaptee();
        var adapter = new Adaptor(specific, new AdaptedEventHandlerManager());

        adapter.AdaptedEvent1 += OnAdaptedEvent1;
        adapter.AdaptedEvent2 += OnAdaptedEvent2;

        specific.FireEvent1();
        specific.FireEvent2();

        Console.ReadLine();
    }

    private static void OnAdaptedEvent1(object sender, AdaptedEventArgs1 args)
    {
        Console.WriteLine($"{nameof(OnAdaptedEvent1)}({sender}, {args.A}, {args.B})");
    }

    private static void OnAdaptedEvent2(object sender, AdaptedEventArgs2 args)
    {
        Console.WriteLine($"{nameof(OnAdaptedEvent2)}({sender}, {args.A})");
    }
}

这就是它应该如何工作的。我注册了我的 Adaptor 事件我的代码中有,当第 3 方库 ( SpecificAdaptee ) 触发它自己的事件时,事件被触发(在此示例中,通过调用 specific.FireEvent1() 和 2 触发)。

为了完整起见,您可以自己尝试,我包含了 AdaptedEventHandlerManager 的代码将适应的事件处理程序映射到 SpecificAdaptee的处理程序,因此我可以像往常一样注册和取消注册多个事件处理程序:

interface IAdaptedEventHandlerManager
{
    TSpecificEventHandler RegisterEventHandler<TSpecificEventHandler>(object adaptedEventHandler,
        TSpecificEventHandler specificEventHandler);

    TSpecificEventHandler UnregisterEventHandler<TSpecificEventHandler>(object adaptedEventHandler)
        where TSpecificEventHandler : class;
}

class AdaptedEventHandlerManager : IAdaptedEventHandlerManager
{
    /// <summary>
    ///     Remembers relation between the specific handler and general handler. Important when unsubscribing from
    ///     events. Key is the general event handler we are registering to events of this class. Value are specific
    ///     event handlers.
    /// </summary>
    private readonly Dictionary<object, List<object>> _eventHandlers =
        new Dictionary<object, List<object>>();

    public TSpecificEventHandler RegisterEventHandler<TSpecificEventHandler>(object adaptedEventHandler,
        TSpecificEventHandler specificEventHandler)
    {
        List<object> eventHandlerList;
        if (!_eventHandlers.TryGetValue(adaptedEventHandler, out eventHandlerList))
        {
            eventHandlerList = new List<object> { specificEventHandler };
            _eventHandlers.Add(adaptedEventHandler, eventHandlerList);
        }
        else
        {
            eventHandlerList.Add(specificEventHandler);
        }
        return specificEventHandler;
    }

    public TSpecificEventHandler UnregisterEventHandler<TSpecificEventHandler>(object adaptedEventHandler)
        where TSpecificEventHandler : class
    {
        List<object> eventHandlerList;
        if (!_eventHandlers.TryGetValue(adaptedEventHandler, out eventHandlerList))
        {
            return null;
        }

        var eventHandler = eventHandlerList.FirstOrDefault();
        if (eventHandler != null)
        {
            eventHandlerList.Remove(eventHandler);
        }

        if (!eventHandlerList.Any())
        {
            _eventHandlers.Remove(adaptedEventHandler);
        }
        return eventHandler as TSpecificEventHandler;
    }
}

这基本上在字典中记住了改编的事件处理程序,以及SpecificAdaptee的列表。的处理程序。

所以我的问题是:有没有一种方法可以调整采取 ref 的事件?参数不缩回自定义delegate需要 ref 的类型参数,所以我可以使用标准EventHandler<>自定义类 EventArgs后裔?

我意识到这是相当多的代码,所以如果有什么不清楚的地方请告诉我。提前致谢。

最佳答案

事件中的

ref 参数用于从订阅者设置。虽然这是个坏主意,但您正在使用的 API 是基于此工作的。

您可以解决适配器类中的所有问题并使其正常工作,这样消费者就不会被 ref 参数污染。他们可以继续使用 EventArgs 样式的事件。

public event EventHandler<AdaptedEventArgs2> AdaptedEvent2
{
    add
    {
        _specificAdaptee.Event2 += _adaptedEventHandlerManager.RegisterEventHandler<AdapteeEventHandler2>(value,
            (SpecificAdaptee sender, ref int a) =>
                {
                    var args = new AdaptedEventArgs2 { A = a };
                    value.Invoke(this, args);
                    a = args.A;
                });
    }
    remove
    {
        _specificAdaptee.Event2 -= _adaptedEventHandlerManager.UnregisterEventHandler<AdapteeEventHandler2>(value);
    }
}

事件执行后,我们将A的值设置为ref参数a。这模拟了 ref 参数的行为,并将其抽象在适配器类下。如果在事件处理程序中更改了 A,它也会反射(reflect)在 SpecificAdaptee 类中。

要展示它如何像 ref 参数一样工作:

class SpecificAdaptee
{
    ...
    public void FireEvent2()
    {
        int a = 42;
        if (Event2 != null)
            Event2(this, ref a);

        Console.WriteLine("A value after the event is {0}", a);
    }
}

private static void OnAdaptedEvent2(object sender, AdaptedEventArgs2 args)
{
    Console.WriteLine($"{nameof(OnAdaptedEvent2)}({sender}, {args.A})");
    args.A = 15;
}

这打印:

A value after the event is 15

PS:为简洁起见,我只添加了程序中需要更改的部分。

关于c# - 适配包含 ref 参数的 C# 事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40126943/

相关文章:

ruby-on-rails - 多态 Ruby SimpleDelegator

events - 使用协程进行事件处理的正确方法是什么?

node.js - Node 包 'events' 的浏览器兼容性是什么?

c# - 如何将简单的字符串值绑定(bind)到文本框?

c# - 将 List<T> 转换为 HashTable

c# - dotnet 核心 File.Move 问题

c# - 如何在 C# 中使用 "param"让这个例子工作?

javascript - 使用 `this` 的 JS 对象给出了未定义的结果

python - 我应该以及如何在 python 中向 int 添加方法吗?

events - GWT 事件预览与事件处理程序