Skip to content

Commit

Permalink
WAF WeakEvent add support for CollectionChanging and CollectionItemCh…
Browse files Browse the repository at this point in the history
…anged
  • Loading branch information
jbe2277 committed Dec 3, 2023
1 parent a367b43 commit 1fac4a9
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class Manager : IManager<Publisher, Subscriber>

public class Publisher : IPublisher, INotifyCollectionChanged
{
private static readonly NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
private static readonly NotifyCollectionChangedEventArgs args = new(NotifyCollectionChangedAction.Reset);
private NotifyCollectionChangedEventHandler? collectionChanged;

public int EventHandlerCount { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Specialized;
using System.Waf.Foundation;
using System.Waf.UnitTesting;

namespace Test.Waf.Foundation
{
[TestClass]
public class WeakEventCollectionChangingTest : WeakEventTestBase<WeakEventCollectionChangingTest.Manager, WeakEventCollectionChangingTest.Publisher, WeakEventCollectionChangingTest.Subscriber>
{
[TestMethod]
public void WeakEventAddArgumentException()
{
AssertHelper.ExpectedException<ArgumentNullException>(() => WeakEvent.CollectionChanging.Add(null!, (s, h) => { }));
AssertHelper.ExpectedException<ArgumentNullException>(() => WeakEvent.CollectionChanging.Add(new Publisher(), null!));
}

public class Manager : IManager<Publisher, Subscriber>
{
public IWeakEventProxy? Proxy { get; set; }

public void Add(Publisher publisher, Subscriber subscriber) => Proxy = WeakEvent.CollectionChanging.Add(publisher, subscriber.Handler);
}

public class Publisher : IPublisher, INotifyCollectionChanging
{
private static readonly NotifyCollectionChangedEventArgs args = new(NotifyCollectionChangedAction.Reset);
private NotifyCollectionChangedEventHandler? collectionChanging;

public int EventHandlerCount { get; private set; }

public event NotifyCollectionChangedEventHandler? CollectionChanging
{
add
{
collectionChanging += value;
EventHandlerCount++;
}
remove
{
collectionChanging -= value;
EventHandlerCount--;
}
}

public void RaiseEvent() => collectionChanging?.Invoke(this, args);
}

public class Subscriber : ISubscriber
{
public int HandlerCallCount { get; set; }

public void Handler(object? sender, NotifyCollectionChangedEventArgs e) => HandlerCallCount++;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.ComponentModel;
using System.Waf.Foundation;
using System.Waf.UnitTesting;

namespace Test.Waf.Foundation
{
[TestClass]
public class WeakEventCollectionItemChangedTest : WeakEventTestBase<WeakEventCollectionItemChangedTest.Manager, WeakEventCollectionItemChangedTest.Publisher, WeakEventCollectionItemChangedTest.Subscriber>
{
[TestMethod]
public void WeakEventAddArgumentException()
{
AssertHelper.ExpectedException<ArgumentNullException>(() => WeakEvent.CollectionItemChanged.Add(null!, (s, h) => { }));
AssertHelper.ExpectedException<ArgumentNullException>(() => WeakEvent.CollectionItemChanged.Add(new Publisher(), null!));
}

public class Manager : IManager<Publisher, Subscriber>
{
public IWeakEventProxy? Proxy { get; set; }

public void Add(Publisher publisher, Subscriber subscriber) => Proxy = WeakEvent.CollectionItemChanged.Add(publisher, subscriber.Handler);
}

public class Publisher : IPublisher, INotifyCollectionItemChanged
{
private static readonly PropertyChangedEventArgs args = new("Test");
private PropertyChangedEventHandler? collectionItemChanged;

public int EventHandlerCount { get; private set; }

public event PropertyChangedEventHandler? CollectionItemChanged
{
add
{
collectionItemChanged += value;
EventHandlerCount++;
}
remove
{
collectionItemChanged -= value;
EventHandlerCount--;
}
}

public void RaiseEvent() => collectionItemChanged?.Invoke(this, args);
}

public class Subscriber : ISubscriber
{
public int HandlerCallCount { get; set; }

public void Handler(object? sender, PropertyChangedEventArgs e) => HandlerCallCount++;
}
}
}
110 changes: 104 additions & 6 deletions src/System.Waf/System.Waf/System.Waf.Core/Foundation/WeakEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ public static IWeakEventProxy Add<TSource>(TSource source, S.EventHandler target
return new WeakEventProxy<TSource>(source, targetHandler, subscribe, unsubscribe);
}

private sealed class WeakEventProxy<TSource> : IWeakEventProxy
where TSource : class
private sealed class WeakEventProxy<TSource> : IWeakEventProxy where TSource : class
{
private readonly WeakReference<TSource> source;
private readonly WeakReference<S.EventHandler> weakTargetHandler;
Expand Down Expand Up @@ -107,8 +106,7 @@ public static IWeakEventProxy Add<TSource>(TSource source, S.EventHandler<TArgs>
return new WeakEventProxy<TSource>(source, targetHandler, subscribe, unsubscribe);
}

private sealed class WeakEventProxy<TSource> : IWeakEventProxy
where TSource : class
private sealed class WeakEventProxy<TSource> : IWeakEventProxy where TSource : class
{
private readonly WeakReference<TSource> source;
private readonly WeakReference<S.EventHandler<TArgs>> weakTargetHandler;
Expand Down Expand Up @@ -295,6 +293,56 @@ private void ProxyHandler(object? sender, PropertyChangedEventArgs e)
}
}

/// <summary>Supports listening to INotifyCollectionChanging.CollectionChanging events via a weak reference.</summary>
public static class CollectionChanging
{
/// <summary>Add an event handler which is kept only by a weak reference.</summary>
/// <param name="source">The source (publisher).</param>
/// <param name="targetHandler">The target event handler of the subscriber.</param>
/// <returns>The created proxy object.</returns>
public static IWeakEventProxy Add(INotifyCollectionChanging source, NotifyCollectionChangedEventHandler targetHandler)
{
if (source is null) throw new ArgumentNullException(nameof(source));
if (targetHandler is null) throw new ArgumentNullException(nameof(targetHandler));
return new WeakEventProxy(source, targetHandler, (s, h) => s.CollectionChanging += h, (s, h) => s.CollectionChanging -= h);
}

private sealed class WeakEventProxy : IWeakEventProxy
{
private readonly WeakReference<INotifyCollectionChanging> source;
private readonly WeakReference<NotifyCollectionChangedEventHandler> weakTargetHandler;
private Action<INotifyCollectionChanging, NotifyCollectionChangedEventHandler>? unsubscribe;

public WeakEventProxy(INotifyCollectionChanging source, NotifyCollectionChangedEventHandler targetHandler, Action<INotifyCollectionChanging, NotifyCollectionChangedEventHandler> subscribe, Action<INotifyCollectionChanging, NotifyCollectionChangedEventHandler> unsubscribe)
{
this.source = new(source);
weakTargetHandler = new(targetHandler);
this.unsubscribe = unsubscribe;
subscribe(source, ProxyHandler);
weakTable.Add(targetHandler);
}

public void Remove()
{
if (RemoveCore() && weakTargetHandler.TryGetTarget(out var targetHandler)) weakTable.Remove(targetHandler);
}

private bool RemoveCore()
{
var unsub = Interlocked.Exchange(ref unsubscribe, null);
if (unsub is null) return false;
if (source.TryGetTarget(out var src)) unsub.Invoke(src, ProxyHandler);
return true;
}

private void ProxyHandler(object? sender, NotifyCollectionChangedEventArgs e)
{
if (weakTargetHandler.TryGetTarget(out var targetHandler)) targetHandler(sender, e);
else RemoveCore();
}
}
}

/// <summary>Supports listening to INotifyCollectionChanged.CollectionChanged events via a weak reference.</summary>
public static class CollectionChanged
{
Expand All @@ -317,8 +365,8 @@ private sealed class WeakEventProxy : IWeakEventProxy

public WeakEventProxy(INotifyCollectionChanged source, NotifyCollectionChangedEventHandler targetHandler, Action<INotifyCollectionChanged, NotifyCollectionChangedEventHandler> subscribe, Action<INotifyCollectionChanged, NotifyCollectionChangedEventHandler> unsubscribe)
{
this.source = new WeakReference<INotifyCollectionChanged>(source);
weakTargetHandler = new WeakReference<NotifyCollectionChangedEventHandler>(targetHandler);
this.source = new(source);
weakTargetHandler = new(targetHandler);
this.unsubscribe = unsubscribe;
subscribe(source, ProxyHandler);
weakTable.Add(targetHandler);
Expand All @@ -345,6 +393,56 @@ private void ProxyHandler(object? sender, NotifyCollectionChangedEventArgs e)
}
}

/// <summary>Supports listening to INotifyCollectionItemChanged.CollectionItemChanged events via a weak reference.</summary>
public static class CollectionItemChanged
{
/// <summary>Add an event handler which is kept only by a weak reference.</summary>
/// <param name="source">The source (publisher).</param>
/// <param name="targetHandler">The target event handler of the subscriber.</param>
/// <returns>The created proxy object.</returns>
public static IWeakEventProxy Add(INotifyCollectionItemChanged source, PropertyChangedEventHandler targetHandler)
{
if (source is null) throw new ArgumentNullException(nameof(source));
if (targetHandler is null) throw new ArgumentNullException(nameof(targetHandler));
return new WeakEventProxy(source, targetHandler, (s, h) => s.CollectionItemChanged += h, (s, h) => s.CollectionItemChanged -= h);
}

private sealed class WeakEventProxy : IWeakEventProxy
{
private readonly WeakReference<INotifyCollectionItemChanged> source;
private readonly WeakReference<PropertyChangedEventHandler> weakTargetHandler;
private Action<INotifyCollectionItemChanged, PropertyChangedEventHandler>? unsubscribe;

public WeakEventProxy(INotifyCollectionItemChanged source, PropertyChangedEventHandler targetHandler, Action<INotifyCollectionItemChanged, PropertyChangedEventHandler> subscribe, Action<INotifyCollectionItemChanged, PropertyChangedEventHandler> unsubscribe)
{
this.source = new(source);
weakTargetHandler = new(targetHandler);
this.unsubscribe = unsubscribe;
subscribe(source, ProxyHandler);
weakTable.Add(targetHandler);
}

public void Remove()
{
if (RemoveCore() && weakTargetHandler.TryGetTarget(out var targetHandler)) weakTable.Remove(targetHandler);
}

private bool RemoveCore()
{
var unsub = Interlocked.Exchange(ref unsubscribe, null);
if (unsub is null) return false;
if (source.TryGetTarget(out var src)) unsub.Invoke(src, ProxyHandler);
return true;
}

private void ProxyHandler(object? sender, PropertyChangedEventArgs e)
{
if (weakTargetHandler.TryGetTarget(out var targetHandler)) targetHandler(sender, e);
else RemoveCore();
}
}
}

/// <summary>Supports listening to ICommand.CanExecuteChanged events via a weak reference.</summary>
public static class CanExecuteChanged
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@
[assembly: SuppressMessage("Design", "CA1063:Implement IDisposable Correctly", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Presentation.Services.SettingsService.Dispose(System.Boolean)")]
[assembly: SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Applications.IDelegateCommand.RaiseCanExecuteChanged")]
[assembly: SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Applications.DelegateCommand.RaiseCanExecuteChanged(System.Waf.Applications.IDelegateCommand[])")]
[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "<Pending>", Scope = "type", Target = "~T:System.Waf.Foundation.WeakEvent.CollectionChanging")]
[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "<Pending>", Scope = "member", Target = "~M:System.Waf.Foundation.WeakEvent.CollectionItemChanged.Add(System.Waf.Foundation.INotifyCollectionItemChanged,System.ComponentModel.PropertyChangedEventHandler)~System.Waf.Foundation.IWeakEventProxy")]
[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "<Pending>", Scope = "type", Target = "~T:System.Waf.Foundation.WeakEvent.CollectionItemChanged")]

0 comments on commit 1fac4a9

Please sign in to comment.