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

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

问题描述

我处在必须使用包含很多事件且写得不好的3rd party库的情况下.它触发了我必须在代码中处理的事件,但是我试图将其抽象化(以便能够对依赖于该库的其余代码进行单元测试),因此我需要一个适配器.问题在于某些事件属于具有 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< AdaptedEventArgs1> 包装在 AdapteeEventHandler1 中并将其注册到 SpecificAdaptee.Event1 ,还将 AdaptedEventArgs1 转换为 AdapteeEventHandler1 所需的参数列表.这样,用户可以注册到 Adaptor 的事件,该事件将在 SpecificAdaptee 触发其自己的事件时触发.接下来,我将发布一个执行此操作的程序,但请注意问题出在 AdaptedEvent2 中,我想以类似的方式执行操作,但是我不知道如何处理 ref 参数( AdaptedEvent2 add 访问器中存在语法错误.这是一个执行项目的控制台应用程序:

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 参数的事件,而不会缩回为采用 ref delegate 类型>参数,因此我可以将标准 EventHandler<> 类与自定义 EventArgs 后代一起使用?

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天全站免登陆