调整包含ref参数

时间:2016-10-19 09:09:58

标签: c# oop events adapter abstraction

我遇到的情况是我必须使用包含大量事件的第三方库,并且写得不是很好。它会激活我必须在我的代码中处理的事件,但是我试图将它抽象出来(为了能够对依赖于该库的其余代码进行单元测试)所以我需要一个适配器。问题是某些事件属于委托类型,需要ref个参数。以下是第三方库的示例:

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,同时转换{ {1}} AdaptedEventArgs1所需参数列表。这样,用户可以注册AdapteeEventHandler1Adaptor触发自己的事件时将被触发的事件。接下来我将发布一个练习这个的程序,但请注意问题出在SpecificAdaptee,我想以类似的方式做事,但我不知道如何处理{{1参数(AdaptedEvent2访问者ref中存在语法错误。 这是一个执行项目的控制台应用程序:

add

这就是它应该如何运作的。我在我的代码中注册了我AdaptedEvent2的事件,当第三方库(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})"); } } )触发自己的事件时,事件被触发(在此示例中,通过调用{{1触发) }和2)。

为了完整性,所以你可以自己尝试一下我包含Adaptor的代码,它将适应的事件处理程序映射到SpecificAdaptee的处理程序,所以我可以注册和取消注册多个事件处理程序,比如我通常会这样做:

specific.FireEvent1()

这基本上会在字典中记住适应的事件处理程序,以及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后代吗?

我意识到它的代码非常少,所以如果不清楚,请告诉我。提前谢谢。

1 个答案:

答案 0 :(得分:2)

事件中的

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,它也会反映在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:为简洁起见,我只添加了需要更改的程序部分。