From 89a9424ec311f2b1705dab867f9359e83941ca52 Mon Sep 17 00:00:00 2001 From: TORISOUP Date: Wed, 5 Jun 2024 18:13:34 +0900 Subject: [PATCH 1/2] add IReadOnlyObservableDictionary interface --- src/ObservableCollections/IObservableCollection.cs | 5 +++++ src/ObservableCollections/ObservableDictionary.cs | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 42c7cec..f7a14e7 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -15,6 +15,11 @@ namespace ObservableCollections ISynchronizedView CreateView(Func transform, bool reverse = false); } + public interface IReadOnlyObservableDictionary : + IReadOnlyDictionary, IObservableCollection> + { + } + public interface IFreezedCollection { ISynchronizedView CreateView(Func transform, bool reverse = false); diff --git a/src/ObservableCollections/ObservableDictionary.cs b/src/ObservableCollections/ObservableDictionary.cs index b052988..897ac7e 100644 --- a/src/ObservableCollections/ObservableDictionary.cs +++ b/src/ObservableCollections/ObservableDictionary.cs @@ -6,8 +6,8 @@ using System.Diagnostics.CodeAnalysis; namespace ObservableCollections { - public sealed partial class ObservableDictionary - : IDictionary, IReadOnlyDictionary, IObservableCollection> + public sealed partial class ObservableDictionary : IDictionary, + IReadOnlyObservableDictionary where TKey : notnull { readonly Dictionary dictionary; From ecce6be78614db21dd9df2aca55a23ef66258b17 Mon Sep 17 00:00:00 2001 From: TORISOUP Date: Wed, 5 Jun 2024 18:56:14 +0900 Subject: [PATCH 2/2] add ObservableDictionaryR3Extensions --- .../ObservableCollectionR3Extensions.cs | 132 ++++++++++++++++++ .../ObservableCollectionExtensionsTest.cs | 132 +++++++++++++++--- 2 files changed, 241 insertions(+), 23 deletions(-) diff --git a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs index 95df7d7..724e2b0 100644 --- a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs +++ b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Threading; using R3; @@ -10,6 +11,13 @@ public readonly record struct CollectionRemoveEvent(int Index, T Value); public readonly record struct CollectionReplaceEvent(int Index, T OldValue, T NewValue); public readonly record struct CollectionMoveEvent(int OldIndex, int NewIndex, T Value); +public readonly record struct DictionaryAddEvent(TKey Key, TValue Value); + +public readonly record struct DictionaryRemoveEvent(TKey Key, TValue Value); + +public readonly record struct DictionaryReplaceEvent(TKey Key, TValue OldValue, TValue NewValue); + + public static class ObservableCollectionR3Extensions { public static Observable> ObserveAdd(this IObservableCollection source, CancellationToken cancellationToken = default) @@ -43,6 +51,26 @@ public static class ObservableCollectionR3Extensions } } +public static class ObservableDictionaryR3Extensions +{ + public static Observable> ObserveDictionaryAdd(this IReadOnlyObservableDictionary source, + CancellationToken cancellationToken = default) + { + return new ObservableDictionaryAdd(source, cancellationToken); + } + + public static Observable> ObserveDictionaryRemove(this IReadOnlyObservableDictionary source, + CancellationToken cancellationToken = default) + { + return new ObservableDictionaryRemove(source, cancellationToken); + } + public static Observable> ObserveDictionaryReplace(this IReadOnlyObservableDictionary source, + CancellationToken cancellationToken = default) + { + return new ObservableDictionaryReplace(source, cancellationToken); + } +} + sealed class ObservableCollectionAdd(IObservableCollection collection, CancellationToken cancellationToken) : Observable> { @@ -225,6 +253,110 @@ sealed class ObservableCollectionCountChanged(IObservableCollection collec } } +sealed class ObservableDictionaryAdd( + IReadOnlyObservableDictionary dictionary, + CancellationToken cancellationToken) : Observable> +{ + protected override IDisposable SubscribeCore(Observer> observer) + { + return new _DictionaryCollectionAdd(dictionary, observer, cancellationToken); + } + + sealed class _DictionaryCollectionAdd( + IObservableCollection> collection, + Observer> observer, + CancellationToken cancellationToken) : + ObservableCollectionObserverBase, DictionaryAddEvent>(collection, + observer, cancellationToken) + { + protected override void Handler(in NotifyCollectionChangedEventArgs> eventArgs) + { + if (eventArgs.Action == NotifyCollectionChangedAction.Add) + { + if (eventArgs.IsSingleItem) + { + observer.OnNext( + new DictionaryAddEvent(eventArgs.NewItem.Key, eventArgs.NewItem.Value)); + } + else + { + var i = eventArgs.NewStartingIndex; + foreach (var item in eventArgs.NewItems) + { + observer.OnNext(new DictionaryAddEvent(item.Key, item.Value)); + } + } + } + } + } +} + +sealed class ObservableDictionaryRemove( + IReadOnlyObservableDictionary dictionary, + CancellationToken cancellationToken) : Observable> +{ + protected override IDisposable SubscribeCore(Observer> observer) + { + return new _DictionaryCollectionRemove(dictionary, observer, cancellationToken); + } + + sealed class _DictionaryCollectionRemove( + IObservableCollection> collection, + Observer> observer, + CancellationToken cancellationToken) : + ObservableCollectionObserverBase, DictionaryRemoveEvent>(collection, + observer, cancellationToken) + { + protected override void Handler(in NotifyCollectionChangedEventArgs> eventArgs) + { + if (eventArgs.Action == NotifyCollectionChangedAction.Remove) + { + if (eventArgs.IsSingleItem) + { + observer.OnNext( + new DictionaryRemoveEvent(eventArgs.OldItem.Key, eventArgs.OldItem.Value)); + } + else + { + var i = eventArgs.NewStartingIndex; + foreach (var item in eventArgs.NewItems) + { + observer.OnNext(new DictionaryRemoveEvent(item.Key, item.Value)); + } + } + } + } + } +} + +sealed class ObservableDictionaryReplace( + IReadOnlyObservableDictionary dictionary, + CancellationToken cancellationToken) : Observable> +{ + protected override IDisposable SubscribeCore(Observer> observer) + { + return new _DictionaryCollectionReplace(dictionary, observer, cancellationToken); + } + + sealed class _DictionaryCollectionReplace( + IObservableCollection> collection, + Observer> observer, + CancellationToken cancellationToken) : + ObservableCollectionObserverBase, DictionaryReplaceEvent>(collection, + observer, cancellationToken) + { + protected override void Handler(in NotifyCollectionChangedEventArgs> eventArgs) + { + if (eventArgs.Action == NotifyCollectionChangedAction.Replace) + { + observer.OnNext(new DictionaryReplaceEvent( + eventArgs.NewItem.Key, + eventArgs.OldItem.Value, + eventArgs.NewItem.Value)); + } + } + } +} abstract class ObservableCollectionObserverBase : IDisposable { diff --git a/tests/ObservableCollections.R3.Tests/ObservableCollectionExtensionsTest.cs b/tests/ObservableCollections.R3.Tests/ObservableCollectionExtensionsTest.cs index a0885cc..85f9355 100644 --- a/tests/ObservableCollections.R3.Tests/ObservableCollectionExtensionsTest.cs +++ b/tests/ObservableCollections.R3.Tests/ObservableCollectionExtensionsTest.cs @@ -10,7 +10,7 @@ public class ObservableCollectionExtensionsTest public void ObserveAdd() { var events = new List>(); - + var collection = new ObservableList(); var subscription = collection.ObserveAdd().Subscribe(ev => events.Add(ev)); @@ -25,20 +25,20 @@ public class ObservableCollectionExtensionsTest events[1].Value.Should().Be(50); events[2].Index.Should().Be(2); events[2].Value.Should().Be(30); - + subscription.Dispose(); - + collection.Add(100); events.Count.Should().Be(3); } - + [Fact] public void ObserveAdd_CancellationToken() { var cts = new CancellationTokenSource(); var events = new List>(); var result = default(Result?); - + var collection = new ObservableList(); var subscription = collection.ObserveAdd(cts.Token).Subscribe(ev => events.Add(ev), x => result = x); @@ -47,17 +47,43 @@ public class ObservableCollectionExtensionsTest collection.Add(30); events.Count.Should().Be(3); - + cts.Cancel(); result.HasValue.Should().BeTrue(); - + subscription.Dispose(); - + collection.Add(100); events.Count.Should().Be(3); } - + + [Fact] + public void ObserveDictionaryAdd() + { + var events = new List>(); + + var dictionary = new ObservableDictionary(); + + var subscription = dictionary.ObserveDictionaryAdd().Subscribe(ev => events.Add(ev)); + dictionary.Add(0, "zero"); + dictionary.Add(1, "one"); + dictionary.Add(2, "two"); + + events.Count.Should().Be(3); + events[0].Key.Should().Be(0); + events[0].Value.Should().Be("zero"); + events[1].Key.Should().Be(1); + events[1].Value.Should().Be("one"); + events[2].Key.Should().Be(2); + events[2].Value.Should().Be("two"); + + subscription.Dispose(); + + dictionary.Add(4, "four"); + events.Count.Should().Be(3); + } + [Fact] public void ObserveRemove() { @@ -72,16 +98,45 @@ public class ObservableCollectionExtensionsTest events.Count.Should().Be(1); events[0].Index.Should().Be(1); events[0].Value.Should().Be(222); - + cts.Cancel(); result.HasValue.Should().BeTrue(); - + subscription.Dispose(); - + collection.RemoveAt(0); events.Count.Should().Be(1); } - + + [Fact] + public void ObserveDictionaryRemove() + { + var events = new List>(); + var dictionary = new ObservableDictionary + { + { 0, "zero" }, + { 1, "one" }, + { 2, "two" } + }; + var cts = new CancellationTokenSource(); + var result = default(Result?); + + var subscription = dictionary.ObserveDictionaryRemove((cts.Token)).Subscribe(ev => events.Add(ev), x => result = x); + dictionary.Remove(0); + + events.Count.Should().Be(1); + events[0].Key.Should().Be(0); + events[0].Value.Should().Be("zero"); + + cts.Cancel(); + result.HasValue.Should().BeTrue(); + + subscription.Dispose(); + + dictionary.Remove(1); + events.Count.Should().Be(1); + } + [Fact] public void ObserveReplace() { @@ -97,16 +152,46 @@ public class ObservableCollectionExtensionsTest events[0].Index.Should().Be(1); events[0].OldValue.Should().Be(222); events[0].NewValue.Should().Be(999); - + cts.Cancel(); result.HasValue.Should().BeTrue(); - + subscription.Dispose(); collection[1] = 444; events.Count.Should().Be(1); } + [Fact] + public void ObserveDictionaryReplace() + { + var events = new List>(); + var dictionary = new ObservableDictionary() + { + { 0, "zero" }, + { 1, "one" }, + { 2, "two" } + }; + var cts = new CancellationTokenSource(); + var result = default(Result?); + + var subscription = dictionary.ObserveDictionaryReplace(cts.Token).Subscribe(ev => events.Add(ev), x => result = x); + dictionary[1] = "ten"; + + events.Count.Should().Be(1); + events[0].Key.Should().Be(1); + events[0].OldValue.Should().Be("one"); + events[0].NewValue.Should().Be("ten"); + + cts.Cancel(); + result.HasValue.Should().BeTrue(); + + subscription.Dispose(); + + dictionary[1] = "one hundred"; + events.Count.Should().Be(1); + } + [Fact] public void ObserveMove() { @@ -116,17 +201,17 @@ public class ObservableCollectionExtensionsTest var result = default(Result?); var subscription = collection.ObserveMove(cts.Token).Subscribe(ev => events.Add(ev), x => result = x); - + collection.Move(1, 2); events.Count.Should().Be(1); events[0].OldIndex.Should().Be(1); events[0].NewIndex.Should().Be(2); events[0].Value.Should().Be(222); - + cts.Cancel(); result.HasValue.Should().BeTrue(); - + subscription.Dispose(); collection.Move(1, 2); @@ -142,22 +227,22 @@ public class ObservableCollectionExtensionsTest using var _ = collection.ObserveCountChanged().Subscribe(count => events.Add(count)); events.Should().BeEmpty(); - + collection.Add(444); events[0].Should().Be(4); collection.Remove(111); events[1].Should().Be(3); - + collection.Move(0, 1); events.Count.Should().Be(2); collection[0] = 999; events.Count.Should().Be(2); - + collection.Clear(); events[2].Should().Be(0); - + collection.Clear(); events.Count.Should().Be(3); } @@ -168,7 +253,8 @@ public class ObservableCollectionExtensionsTest var events = new List(); var collection = new ObservableList([111, 222, 333]); - var subscription = collection.ObserveCountChanged(notifyCurrentCount: true).Subscribe(count => events.Add(count)); + var subscription = collection.ObserveCountChanged(notifyCurrentCount: true) + .Subscribe(count => events.Add(count)); events[0].Should().Be(3); } } \ No newline at end of file