Skip to content

Commit

Permalink
refactor: Concrete versions of StartStopDelegateWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed Aug 26, 2024
1 parent 2c25191 commit a9d3360
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ public class Given_StartStopEventWrapper
[TestMethod]
public void When_Default_Event_Null()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
Assert.IsNull(wrapper.Event);
}

[TestMethod]
public void When_Add_Handler_Event_Not_Null()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => { });
Assert.IsNotNull(wrapper.Event);
}
Expand All @@ -31,7 +31,7 @@ public void When_Add_Remove_Event_Null()
void Handler(Accelerometer a, AccelerometerReadingChangedEventArgs e)
{
}
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.AddHandler(Handler);
wrapper.RemoveHandler(Handler);
Assert.IsNull(wrapper.Event);
Expand All @@ -40,7 +40,7 @@ void Handler(Accelerometer a, AccelerometerReadingChangedEventArgs e)
[TestMethod]
public void When_Remove_On_Empty()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.RemoveHandler((s, e) => { });
Assert.IsNull(wrapper.Event);
}
Expand All @@ -49,7 +49,7 @@ public void When_Remove_On_Empty()
[TestMethod]
public void When_Remove_Nonexistent()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => { });
wrapper.RemoveHandler((s, e) => { });
Assert.IsNotNull(wrapper.Event);
Expand All @@ -59,7 +59,7 @@ public void When_Remove_Nonexistent()
public void When_Single_Invoked()
{
var invoked = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => invoked = true);
Assert.IsFalse(invoked);
wrapper.Event?.Invoke(null, EventArgs.Empty);
Expand All @@ -71,7 +71,7 @@ public void When_Multiple_Invoked()
{
var firstInvoked = false;
var secondInvoked = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => firstInvoked = true);
wrapper.AddHandler((s, e) => secondInvoked = true);
Assert.IsFalse(firstInvoked);
Expand All @@ -90,7 +90,7 @@ void FirstHandler(object sender, EventArgs args)
firstInvoked = true;
}
var secondInvoked = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => { });
wrapper.AddHandler(FirstHandler);
wrapper.AddHandler((s, e) => secondInvoked = true);
Assert.IsFalse(firstInvoked);
Expand All @@ -105,7 +105,7 @@ void FirstHandler(object sender, EventArgs args)
public void When_First_Subscriber_OnFirst_Invoked()
{
var onFirst = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => onFirst = true, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => onFirst = true, () => { });
Assert.IsFalse(onFirst);
wrapper.AddHandler((s, e) => { });
Assert.IsTrue(onFirst);
Expand All @@ -115,7 +115,7 @@ public void When_First_Subscriber_OnFirst_Invoked()
public void When_Multiple_Subscribers_OnFirst_Once()
{
var onFirstCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
Assert.AreEqual(0, onFirstCounter);
wrapper.AddHandler((s, e) => { });
wrapper.AddHandler((s, e) => { });
Expand All @@ -134,7 +134,7 @@ void SecondSubscriber(object sender, EventArgs e)
}

var onFirstCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
Assert.AreEqual(0, onFirstCounter);
wrapper.AddHandler(FirstSubscriber);
wrapper.AddHandler(SecondSubscriber);
Expand All @@ -153,7 +153,7 @@ void FirstSubscriber(object sender, EventArgs e)
{
}
var onLast = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLast = true);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLast = true);
wrapper.AddHandler(FirstSubscriber);
Assert.IsFalse(onLast);
wrapper.RemoveHandler(FirstSubscriber);
Expand All @@ -171,7 +171,7 @@ void SecondSubscriber(object sender, EventArgs e)
{
}
var onLastCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
wrapper.AddHandler(FirstSubscriber);
wrapper.AddHandler(SecondSubscriber);
Assert.AreEqual(0, onLastCounter);
Expand All @@ -188,7 +188,7 @@ void FirstSubscriber(object sender, EventArgs e)
}

var onLastCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
wrapper.AddHandler(FirstSubscriber);
Assert.AreEqual(0, onLastCounter);
wrapper.RemoveHandler(FirstSubscriber);
Expand All @@ -208,7 +208,7 @@ void SecondSubscriber(object sender, EventArgs e)
}

var onLastCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
wrapper.AddHandler(FirstSubscriber);
wrapper.AddHandler(SecondSubscriber);
Assert.AreEqual(0, onLastCounter);
Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UWP/Devices/Sensors/Barometer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ public partial class Barometer
private static bool _initializationAttempted;
private static Barometer _instance;

private StartStopEventWrapper<TypedEventHandler<Barometer, BarometerReadingChangedEventArgs>> _readingChangedWrapper;
private readonly StartStopTypedEventWrapper<Barometer, BarometerReadingChangedEventArgs> _readingChangedWrapper;

/// <summary>
/// Hides the public parameterless constructor
/// </summary>
private Barometer()
{
_readingChangedWrapper = new StartStopEventWrapper<TypedEventHandler<Barometer, BarometerReadingChangedEventArgs>>(
_readingChangedWrapper = new StartStopTypedEventWrapper<Barometer, BarometerReadingChangedEventArgs>(
() => StartReading(),
() => StopReading(),
_syncLock);
Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UWP/Devices/Sensors/HingeAngleSensor.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ public partial class HingeAngleSensor
private static HingeAngleSensor _instance;
private static INativeHingeAngleSensor _hingeAngleSensor;

private StartStopEventWrapper<TypedEventHandler<HingeAngleSensor, HingeAngleSensorReadingChangedEventArgs>> _readingChanged;
private readonly StartStopTypedEventWrapper<HingeAngleSensor, HingeAngleSensorReadingChangedEventArgs> _readingChanged;

/// <summary>
/// Hides the public parameterless constructor
/// </summary>
private HingeAngleSensor()
{
_readingChanged = new StartStopEventWrapper<TypedEventHandler<HingeAngleSensor, HingeAngleSensorReadingChangedEventArgs>>(
_readingChanged = new StartStopTypedEventWrapper<HingeAngleSensor, HingeAngleSensorReadingChangedEventArgs>(
() => StartReading(),
() => StopReading(),
_syncLock);
Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UWP/Devices/Sensors/LightSensor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public partial class LightSensor
{
private readonly static Lazy<LightSensor?> _instance = new Lazy<LightSensor?>(() => TryCreateInstance());

private readonly StartStopEventWrapper<TypedEventHandler<LightSensor, LightSensorReadingChangedEventArgs>> _readingChangedWrapper;
private readonly StartStopTypedEventWrapper<LightSensor, LightSensorReadingChangedEventArgs> _readingChangedWrapper;

private LightSensor()
{
_readingChangedWrapper = new StartStopEventWrapper<TypedEventHandler<LightSensor, LightSensorReadingChangedEventArgs>>(
_readingChangedWrapper = new StartStopTypedEventWrapper<LightSensor, LightSensorReadingChangedEventArgs>(
StartReading, StopReading);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UWP/Devices/Sensors/Magnetometer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ public partial class Magnetometer
private static Magnetometer _instance;
private static bool _initializationAttempted;

private StartStopEventWrapper<TypedEventHandler<Magnetometer, MagnetometerReadingChangedEventArgs>> _readingChangedWrapper;
private StartStopTypedEventWrapper<Magnetometer, MagnetometerReadingChangedEventArgs> _readingChangedWrapper;

private Magnetometer()
{
_readingChangedWrapper = new StartStopEventWrapper<TypedEventHandler<Magnetometer, MagnetometerReadingChangedEventArgs>>(
_readingChangedWrapper = new StartStopTypedEventWrapper<Magnetometer, MagnetometerReadingChangedEventArgs>(
() => StartReading(),
() => StopReading(),
_syncLock);
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UWP/Devices/Sensors/Pedometer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public partial class Pedometer
private static bool _initializationAttempted;
private static Task<Pedometer> _instanceTask;

private StartStopEventWrapper<TypedEventHandler<Pedometer, PedometerReadingChangedEventArgs>> _readingChangedWrapper;
private readonly StartStopTypedEventWrapper<Pedometer, PedometerReadingChangedEventArgs> _readingChangedWrapper;

/// <summary>
/// Hides the public parameterless constructor
Expand Down
74 changes: 74 additions & 0 deletions src/Uno.UWP/Helpers/StartStopDelegateWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#nullable enable

using System;

namespace Uno.Helpers
{
/// <summary>
/// Creates a wrapper around a generic event, which needs to be synchronized
/// and needs to run an action when first subscriber is added and when
/// last subscriber is removed. The operations executed when first subscriber
/// is added and last subscriber is removed will execute within
/// a synchronization lock, so please avoid blocking within the actions.
/// </summary>
internal class StartStopDelegateWrapper<TDelegate>
where TDelegate : Delegate
{
private readonly object _syncLock;
private readonly Action _onFirst;
private readonly Action _onLast;

/// <summary>
/// Creates a new instance of start-stop delegate wrapper.
/// </summary>
/// <param name="onFirst">Action to run when first subscriber is added.
/// This will run within a synchronization lock so it should not involve blocking operations.</param>
/// <param name="onLast">Action to run when last subscriber is removed.
/// This will run within a synchronization lock so it should not involve blocking operations.</param>
/// <param name="sharedLock">Optional shared object to lock on (when multiple events
/// rely on the same native platform operation.</param>
public StartStopDelegateWrapper(
Action onFirst,
Action onLast,
object? sharedLock = null)
{
_onFirst = onFirst;
_onLast = onLast;
_syncLock = sharedLock ?? new object();
}

public TDelegate? Event { get; private set; } = null;

public object SyncLock => _syncLock;

public void AddHandler(TDelegate handler)
{
lock (_syncLock)
{
var firstSubscriber = Event == null;
Event = (TDelegate)Delegate.Combine(Event, handler);
if (firstSubscriber)
{
_onFirst();
}
}
}

public void RemoveHandler(TDelegate handler)
{
lock (_syncLock)
{
if (Event != null)
{
Event = (TDelegate?)Delegate.Remove(Event, handler);
if (Event == null)
{
_onLast();
}
}
}
}

public bool IsActive => Event != null;
}
}
64 changes: 11 additions & 53 deletions src/Uno.UWP/Helpers/StartStopEventWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,11 @@
namespace Uno.Helpers
{
/// <summary>
/// Creates a wrapper around an event, which needs to be synchronized
/// and needs to run an action when first subscriber is added and when
/// last subscriber is removed. The operations executed when first subscriber
/// is added and last subscriber is removed will execute within
/// a synchronization lock, so please avoid blocking within the actions.
/// Start stop wrapper for EventHandler<T> events.
/// </summary>
internal class StartStopEventWrapper<TDelegate>
where TDelegate : Delegate
/// <typeparam name="TEventArgs">Event args type.</typeparam>
internal class StartStopEventWrapper<TEventArgs> : StartStopDelegateWrapper<EventHandler<TEventArgs>>
{
private readonly object _syncLock;
private readonly Action _onFirst;
private readonly Action _onLast;

/// <summary>
/// Creates a new instance of start-stop event wrapper.
/// </summary>
Expand All @@ -27,50 +19,16 @@ internal class StartStopEventWrapper<TDelegate>
/// This will run within a synchronization lock so it should not involve blocking operations.</param>
/// <param name="sharedLock">Optional shared object to lock on (when multiple events
/// rely on the same native platform operation.</param>
public StartStopEventWrapper(
Action onFirst,
Action onLast,
object? sharedLock = null)
{
_onFirst = onFirst;
_onLast = onLast;
_syncLock = sharedLock ?? new object();
}

public TDelegate? Event { get; private set; }

public bool IsActive => Event != null;

public object SyncLock => _syncLock;

public void AddHandler(TDelegate handler)
{
lock (_syncLock)
{
var firstSubscriber = Event == null;
Event = (TDelegate)Delegate.Combine(Event, handler);
if (firstSubscriber)
{
_onFirst();
}
}
}

public void RemoveHandler(TDelegate handler)
public StartStopEventWrapper(Action onFirst, Action onLast, object? sharedLock = null) :
base(onFirst, onLast, sharedLock)
{
lock (_syncLock)
{
if (Event != null)
{
Event = (TDelegate?)Delegate.Remove(Event, handler);
if (Event == null)
{
_onLast();
}
}
}
}

public bool IsActive => Event != null;
/// <summary>
/// Invokes the event.
/// </summary>
/// <param name="sender">Sender.</param>
/// <param name="args">Args.</param>
public void Invoke(object sender, TEventArgs args) => Event?.Invoke(sender, args);
}
}
35 changes: 35 additions & 0 deletions src/Uno.UWP/Helpers/StartStopTypedEventWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#nullable enable

using System;
using Windows.Foundation;

namespace Uno.Helpers
{
/// <summary>
/// Start stop wrapper for TypedEventHandler<T> events.
/// </summary>
/// <typeparam name="TEventArgs">Event args type.</typeparam>
internal class StartStopTypedEventWrapper<TSender, TResult> : StartStopDelegateWrapper<TypedEventHandler<TSender, TResult>>
{
/// <summary>
/// Creates a new instance of start-stop event wrapper.
/// </summary>
/// <param name="onFirst">Action to run when first subscriber is added.
/// This will run within a synchronization lock so it should not involve blocking operations.</param>
/// <param name="onLast">Action to run when last subscriber is removed.
/// This will run within a synchronization lock so it should not involve blocking operations.</param>
/// <param name="sharedLock">Optional shared object to lock on (when multiple events
/// rely on the same native platform operation.</param>
public StartStopTypedEventWrapper(Action onFirst, Action onLast, object? sharedLock = null) :
base(onFirst, onLast, sharedLock)
{
}

/// <summary>
/// Invokes the event.
/// </summary>
/// <param name="sender">Sender.</param>
/// <param name="args">Args.</param>
public void Invoke(TSender sender, TResult args) => Event?.Invoke(sender, args);
}
}

0 comments on commit a9d3360

Please sign in to comment.