适配包含 ref 参数的 C# 事件
Adapting C# event containing ref parameter
我处于这样一种情况,我必须使用包含很多事件的第 3 方库,恕我直言,写得不是很好。它触发了我必须在我的代码中处理的事件,但我试图将它抽象出来(以便能够对依赖于该库的其余代码进行单元测试)所以我需要一个适配器。问题是某些事件是采用 ref
参数的委托类型。这是第 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
触发它自己的事件时,这些事件将被触发。接下来我将 post 一个程序来练习这个,但请注意问题出在 AdaptedEvent2
,我想以类似的方式做事,但我不知道如何处理 AdaptedEvent2
=14=] 参数(AdaptedEvent2
的 add
访问器中存在语法错误。
这是一个运行该项目的控制台应用程序:
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).
为了完整起见,您可以自己尝试一下会做:
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
参数的事件,而无需缩回到采用 ref
参数的自定义 delegate
类型,所以我可以使用标准 EventHandler<>
class 和自定义 EventArgs
后代?
我知道代码很少,所以如果有什么不清楚的地方请告诉我。提前致谢。
事件中的 ref
参数用于从订阅者设置。虽然这是个坏主意,但您正在使用的 api 是基于此工作的。
您可以解决适配器 class 中的所有问题并使其正常工作,从而使消费者不会被 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 参数的行为,并在适配器 class 下对其进行了抽象。如果在事件处理程序中更改 A
,它也会反映在 SpecificAdaptee
class 中。
展示它如何像 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:为简洁起见,我只添加了您程序中需要更改的部分。
我处于这样一种情况,我必须使用包含很多事件的第 3 方库,恕我直言,写得不是很好。它触发了我必须在我的代码中处理的事件,但我试图将它抽象出来(以便能够对依赖于该库的其余代码进行单元测试)所以我需要一个适配器。问题是某些事件是采用 ref
参数的委托类型。这是第 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
触发它自己的事件时,这些事件将被触发。接下来我将 post 一个程序来练习这个,但请注意问题出在 AdaptedEvent2
,我想以类似的方式做事,但我不知道如何处理 AdaptedEvent2
=14=] 参数(AdaptedEvent2
的 add
访问器中存在语法错误。
这是一个运行该项目的控制台应用程序:
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).
为了完整起见,您可以自己尝试一下会做:
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
参数的事件,而无需缩回到采用 ref
参数的自定义 delegate
类型,所以我可以使用标准 EventHandler<>
class 和自定义 EventArgs
后代?
我知道代码很少,所以如果有什么不清楚的地方请告诉我。提前致谢。
ref
参数用于从订阅者设置。虽然这是个坏主意,但您正在使用的 api 是基于此工作的。
您可以解决适配器 class 中的所有问题并使其正常工作,从而使消费者不会被 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 参数的行为,并在适配器 class 下对其进行了抽象。如果在事件处理程序中更改 A
,它也会反映在 SpecificAdaptee
class 中。
展示它如何像 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:为简洁起见,我只添加了您程序中需要更改的部分。