diff --git a/src/Uno.UI.RuntimeTests/Tests/Uno_Helpers/Given_StartStopEventWrapper.cs b/src/Uno.UI.RuntimeTests/Tests/Uno_Helpers/Given_StartStopEventWrapper.cs index 17aae2894e6e..1d1f93f10ef5 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Uno_Helpers/Given_StartStopEventWrapper.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Uno_Helpers/Given_StartStopEventWrapper.cs @@ -13,14 +13,14 @@ public class Given_StartStopEventWrapper [TestMethod] public void When_Default_Event_Null() { - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); Assert.IsNull(wrapper.Event); } [TestMethod] public void When_Add_Handler_Event_Not_Null() { - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); wrapper.AddHandler((s, e) => { }); Assert.IsNotNull(wrapper.Event); } @@ -31,7 +31,7 @@ public void When_Add_Remove_Event_Null() void Handler(Accelerometer a, AccelerometerReadingChangedEventArgs e) { } - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); wrapper.AddHandler(Handler); wrapper.RemoveHandler(Handler); Assert.IsNull(wrapper.Event); @@ -40,7 +40,7 @@ void Handler(Accelerometer a, AccelerometerReadingChangedEventArgs e) [TestMethod] public void When_Remove_On_Empty() { - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); wrapper.RemoveHandler((s, e) => { }); Assert.IsNull(wrapper.Event); } @@ -49,7 +49,7 @@ public void When_Remove_On_Empty() [TestMethod] public void When_Remove_Nonexistent() { - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); wrapper.AddHandler((s, e) => { }); wrapper.RemoveHandler((s, e) => { }); Assert.IsNotNull(wrapper.Event); @@ -59,7 +59,7 @@ public void When_Remove_Nonexistent() public void When_Single_Invoked() { var invoked = false; - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); wrapper.AddHandler((s, e) => invoked = true); Assert.IsFalse(invoked); wrapper.Event?.Invoke(null, EventArgs.Empty); @@ -71,7 +71,7 @@ public void When_Multiple_Invoked() { var firstInvoked = false; var secondInvoked = false; - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); wrapper.AddHandler((s, e) => firstInvoked = true); wrapper.AddHandler((s, e) => secondInvoked = true); Assert.IsFalse(firstInvoked); @@ -90,7 +90,7 @@ void FirstHandler(object sender, EventArgs args) firstInvoked = true; } var secondInvoked = false; - var wrapper = new StartStopEventWrapper>(() => { }, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => { }); wrapper.AddHandler(FirstHandler); wrapper.AddHandler((s, e) => secondInvoked = true); Assert.IsFalse(firstInvoked); @@ -105,7 +105,7 @@ void FirstHandler(object sender, EventArgs args) public void When_First_Subscriber_OnFirst_Invoked() { var onFirst = false; - var wrapper = new StartStopEventWrapper>(() => onFirst = true, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => onFirst = true, () => { }); Assert.IsFalse(onFirst); wrapper.AddHandler((s, e) => { }); Assert.IsTrue(onFirst); @@ -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>(() => onFirstCounter++, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => onFirstCounter++, () => { }); Assert.AreEqual(0, onFirstCounter); wrapper.AddHandler((s, e) => { }); wrapper.AddHandler((s, e) => { }); @@ -134,7 +134,7 @@ void SecondSubscriber(object sender, EventArgs e) } var onFirstCounter = 0; - var wrapper = new StartStopEventWrapper>(() => onFirstCounter++, () => { }); + var wrapper = new StartStopDelegateWrapper>(() => onFirstCounter++, () => { }); Assert.AreEqual(0, onFirstCounter); wrapper.AddHandler(FirstSubscriber); wrapper.AddHandler(SecondSubscriber); @@ -153,7 +153,7 @@ void FirstSubscriber(object sender, EventArgs e) { } var onLast = false; - var wrapper = new StartStopEventWrapper>(() => { }, () => onLast = true); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => onLast = true); wrapper.AddHandler(FirstSubscriber); Assert.IsFalse(onLast); wrapper.RemoveHandler(FirstSubscriber); @@ -171,7 +171,7 @@ void SecondSubscriber(object sender, EventArgs e) { } var onLastCounter = 0; - var wrapper = new StartStopEventWrapper>(() => { }, () => onLastCounter++); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => onLastCounter++); wrapper.AddHandler(FirstSubscriber); wrapper.AddHandler(SecondSubscriber); Assert.AreEqual(0, onLastCounter); @@ -188,7 +188,7 @@ void FirstSubscriber(object sender, EventArgs e) } var onLastCounter = 0; - var wrapper = new StartStopEventWrapper>(() => { }, () => onLastCounter++); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => onLastCounter++); wrapper.AddHandler(FirstSubscriber); Assert.AreEqual(0, onLastCounter); wrapper.RemoveHandler(FirstSubscriber); @@ -208,7 +208,7 @@ void SecondSubscriber(object sender, EventArgs e) } var onLastCounter = 0; - var wrapper = new StartStopEventWrapper>(() => { }, () => onLastCounter++); + var wrapper = new StartStopDelegateWrapper>(() => { }, () => onLastCounter++); wrapper.AddHandler(FirstSubscriber); wrapper.AddHandler(SecondSubscriber); Assert.AreEqual(0, onLastCounter); diff --git a/src/Uno.UWP/Devices/Sensors/Barometer.cs b/src/Uno.UWP/Devices/Sensors/Barometer.cs index dcad4ee8dfc2..3be7ca343623 100644 --- a/src/Uno.UWP/Devices/Sensors/Barometer.cs +++ b/src/Uno.UWP/Devices/Sensors/Barometer.cs @@ -14,14 +14,14 @@ public partial class Barometer private static bool _initializationAttempted; private static Barometer _instance; - private StartStopEventWrapper> _readingChangedWrapper; + private readonly StartStopTypedEventWrapper _readingChangedWrapper; /// /// Hides the public parameterless constructor /// private Barometer() { - _readingChangedWrapper = new StartStopEventWrapper>( + _readingChangedWrapper = new StartStopTypedEventWrapper( () => StartReading(), () => StopReading(), _syncLock); diff --git a/src/Uno.UWP/Devices/Sensors/HingeAngleSensor.Android.cs b/src/Uno.UWP/Devices/Sensors/HingeAngleSensor.Android.cs index 2a08f47b8c0e..24ce6d6b9754 100644 --- a/src/Uno.UWP/Devices/Sensors/HingeAngleSensor.Android.cs +++ b/src/Uno.UWP/Devices/Sensors/HingeAngleSensor.Android.cs @@ -21,14 +21,14 @@ public partial class HingeAngleSensor private static HingeAngleSensor _instance; private static INativeHingeAngleSensor _hingeAngleSensor; - private StartStopEventWrapper> _readingChanged; + private readonly StartStopTypedEventWrapper _readingChanged; /// /// Hides the public parameterless constructor /// private HingeAngleSensor() { - _readingChanged = new StartStopEventWrapper>( + _readingChanged = new StartStopTypedEventWrapper( () => StartReading(), () => StopReading(), _syncLock); diff --git a/src/Uno.UWP/Devices/Sensors/LightSensor.cs b/src/Uno.UWP/Devices/Sensors/LightSensor.cs index e5f54b8fa827..8342bf353170 100644 --- a/src/Uno.UWP/Devices/Sensors/LightSensor.cs +++ b/src/Uno.UWP/Devices/Sensors/LightSensor.cs @@ -15,11 +15,11 @@ public partial class LightSensor { private readonly static Lazy _instance = new Lazy(() => TryCreateInstance()); - private readonly StartStopEventWrapper> _readingChangedWrapper; + private readonly StartStopTypedEventWrapper _readingChangedWrapper; private LightSensor() { - _readingChangedWrapper = new StartStopEventWrapper>( + _readingChangedWrapper = new StartStopTypedEventWrapper( StartReading, StopReading); } diff --git a/src/Uno.UWP/Devices/Sensors/Magnetometer.cs b/src/Uno.UWP/Devices/Sensors/Magnetometer.cs index 7b363f6474a0..b56ba886844b 100644 --- a/src/Uno.UWP/Devices/Sensors/Magnetometer.cs +++ b/src/Uno.UWP/Devices/Sensors/Magnetometer.cs @@ -14,11 +14,11 @@ public partial class Magnetometer private static Magnetometer _instance; private static bool _initializationAttempted; - private StartStopEventWrapper> _readingChangedWrapper; + private StartStopTypedEventWrapper _readingChangedWrapper; private Magnetometer() { - _readingChangedWrapper = new StartStopEventWrapper>( + _readingChangedWrapper = new StartStopTypedEventWrapper( () => StartReading(), () => StopReading(), _syncLock); diff --git a/src/Uno.UWP/Devices/Sensors/Pedometer.cs b/src/Uno.UWP/Devices/Sensors/Pedometer.cs index 8f9c928316d7..0f6169c0a759 100644 --- a/src/Uno.UWP/Devices/Sensors/Pedometer.cs +++ b/src/Uno.UWP/Devices/Sensors/Pedometer.cs @@ -17,7 +17,7 @@ public partial class Pedometer private static bool _initializationAttempted; private static Task _instanceTask; - private StartStopEventWrapper> _readingChangedWrapper; + private readonly StartStopTypedEventWrapper _readingChangedWrapper; /// /// Hides the public parameterless constructor diff --git a/src/Uno.UWP/Helpers/StartStopDelegateWrapper.cs b/src/Uno.UWP/Helpers/StartStopDelegateWrapper.cs new file mode 100644 index 000000000000..2689b68025e6 --- /dev/null +++ b/src/Uno.UWP/Helpers/StartStopDelegateWrapper.cs @@ -0,0 +1,74 @@ +#nullable enable + +using System; + +namespace Uno.Helpers +{ + /// + /// 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. + /// + internal class StartStopDelegateWrapper + where TDelegate : Delegate + { + private readonly object _syncLock; + private readonly Action _onFirst; + private readonly Action _onLast; + + /// + /// Creates a new instance of start-stop delegate wrapper. + /// + /// Action to run when first subscriber is added. + /// This will run within a synchronization lock so it should not involve blocking operations. + /// Action to run when last subscriber is removed. + /// This will run within a synchronization lock so it should not involve blocking operations. + /// Optional shared object to lock on (when multiple events + /// rely on the same native platform operation. + 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; + } +} diff --git a/src/Uno.UWP/Helpers/StartStopEventWrapper.cs b/src/Uno.UWP/Helpers/StartStopEventWrapper.cs index 32785d2adb5b..ffd402097ed0 100644 --- a/src/Uno.UWP/Helpers/StartStopEventWrapper.cs +++ b/src/Uno.UWP/Helpers/StartStopEventWrapper.cs @@ -5,19 +5,11 @@ namespace Uno.Helpers { /// - /// 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 events. /// - internal class StartStopEventWrapper - where TDelegate : Delegate + /// Event args type. + internal class StartStopEventWrapper : StartStopDelegateWrapper> { - private readonly object _syncLock; - private readonly Action _onFirst; - private readonly Action _onLast; - /// /// Creates a new instance of start-stop event wrapper. /// @@ -27,50 +19,16 @@ internal class StartStopEventWrapper /// This will run within a synchronization lock so it should not involve blocking operations. /// Optional shared object to lock on (when multiple events /// rely on the same native platform operation. - 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; + /// + /// Invokes the event. + /// + /// Sender. + /// Args. + public void Invoke(object sender, TEventArgs args) => Event?.Invoke(sender, args); } } diff --git a/src/Uno.UWP/Helpers/StartStopTypedEventWrapper.cs b/src/Uno.UWP/Helpers/StartStopTypedEventWrapper.cs new file mode 100644 index 000000000000..5b2a53fd9249 --- /dev/null +++ b/src/Uno.UWP/Helpers/StartStopTypedEventWrapper.cs @@ -0,0 +1,35 @@ +#nullable enable + +using System; +using Windows.Foundation; + +namespace Uno.Helpers +{ + /// + /// Start stop wrapper for TypedEventHandler events. + /// + /// Event args type. + internal class StartStopTypedEventWrapper : StartStopDelegateWrapper> + { + /// + /// Creates a new instance of start-stop event wrapper. + /// + /// Action to run when first subscriber is added. + /// This will run within a synchronization lock so it should not involve blocking operations. + /// Action to run when last subscriber is removed. + /// This will run within a synchronization lock so it should not involve blocking operations. + /// Optional shared object to lock on (when multiple events + /// rely on the same native platform operation. + public StartStopTypedEventWrapper(Action onFirst, Action onLast, object? sharedLock = null) : + base(onFirst, onLast, sharedLock) + { + } + + /// + /// Invokes the event. + /// + /// Sender. + /// Args. + public void Invoke(TSender sender, TResult args) => Event?.Invoke(sender, args); + } +}