调整包含 ref 参数的 C# 事件 [英] Adapting C# event containing ref parameter

查看:25
本文介绍了调整包含 ref 参数的 C# 事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm in a situation where I have to use 3rd party library that contains a lot of events and is imho not very well written. It fires up events that I have to handle in my code, but I'm trying to abstract it away (to be able to unit test rest of my code dependent on that library) so I need an adapter. The problem is that some of the events are of delegate type that take ref parameters. Here's an example of how the 3rd party library looks like:

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);
    }
}

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

To show how I am abstracting regular event taking list of parameters, it contains Event1 of type AdapteeEventHandler1. The problematic type is AdapteeEventHandler2, but let me show first how I am going about adapting the whole thing:

#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 包装在 AdapteeEventHandler1 中并将其注册到 SpecificAdaptee.Event1,同时将 AdaptedEventArgs1 转换为 AdapteeEventHandler1 所需的参数列表.通过这种方式,用户可以注册 Adaptor 的事件,当 SpecificAdaptee 触发自己的事件时,这些事件将被触发.接下来我将发布一个程序来练习这个,但请注意问题出在AdaptedEvent2,我想以类似的方式做事,但我不知道如何处理AdaptedEvent2>ref 参数(AdaptedEvent2add 访问器中存在语法错误.这是一个运行该项目的控制台应用程序:

So what is happening here is that when I register an event handler to Adaptor.AdaptedEvent1 I am wrapping EventHandler<AdaptedEventArgs1> in AdapteeEventHandler1 and register it to SpecificAdaptee.Event1, also converting the AdaptedEventArgs1 to list of parameters required by AdapteeEventHandler1. This way user can register to events of Adaptor that will be fired when SpecificAdaptee fires its own events. Next I will post a program that exercises this but note that the problem is in AdaptedEvent2, where I would like to do things in an analogous manner, but I don't know how to deal with the ref parameter (there is a syntax error in add accessor of AdaptedEvent2. Here is a console application exercising the project:

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 触发.

So that's how it's supposed to work. I register to events of my Adaptor that I have in my code, and events get fired when the 3rd party library (SpecificAdaptee) fires its own events (here in this example, triggered by calling specific.FireEvent1() and 2).

为了完整起见,你可以自己尝试一下,我包含了 AdaptedEventHandlerManager 的代码,它将适配的事件处理程序映射到 SpecificAdaptee 的处理程序,所以我可以注册和取消注册多个事件像我通常会做的处理程序:

For completeness, so you can try it yourself I include code for AdaptedEventHandlerManager that maps adapted event handlers to SpecificAdaptee's handlers, so I can register and unregister multiple event handlers like I normally would do:

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 的处理程序列表.

This basically remembers in a dictionary the adapted event handler, and the list of SpecificAdaptee's handlers.

所以我的问题是:是否有一种方法可以调整采用 ref 参数的事件,而无需退回到采用 refdelegate 类型> 参数,所以我可以使用带有自定义 EventArgs 后代的标准 EventHandler<> 类?

So my question: is there a way to adapt events taking ref parameters without retracting to custom delegate type that takes a ref parameter, so I can use standard EventHandler<> class with custom EventArgs descendant?

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

I realise it's quite a handful of code so please let me know if something is not clear. Thanks in advance.

推荐答案

ref 事件中的参数旨在从订阅者设置.虽然这是一个坏主意,但您使用的 api 正是基于此工作的.

ref parameter in the event is meant to set from the subscribers. Though it's a bad idea, the api which you're using works based on that.

您可以承担适配器类的所有痛苦并使其工作,这样消费者就不会被 ref 参数污染.他们可以继续使用 EventArgs 样式的事件.

You can take all the pain in the adapter class and make it work such that consumers are not polluted by the ref parameter. They can continue to use EventArgs style events.

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 在事件处理程序中发生变化,它也会反映在 SpecificAdaptee 类中.

After the event is executed, we set the value of A to the ref parameter a. This simulates the behavior of ref parameter and also abstracts it under the adapter class. If A is changed in the event handler, it will be reflected in the SpecificAdaptee class too.

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

To show how this works like a ref parameter:

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:为简洁起见,我只添加了程序中需要更改的部分.

这篇关于调整包含 ref 参数的 C# 事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆