Merge pull request #45 from TORISOUP/add_readonlyObservableDictionary

Add IReadOnlyObservableDictionary interface and ObservableDictionaryR3Extensions
This commit is contained in:
Yoshifumi Kawai 2024-06-10 14:50:36 +09:00 committed by GitHub
commit 7e139dc57a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 248 additions and 25 deletions

View File

@ -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<T>(int Index, T Value);
public readonly record struct CollectionReplaceEvent<T>(int Index, T OldValue, T NewValue);
public readonly record struct CollectionMoveEvent<T>(int OldIndex, int NewIndex, T Value);
public readonly record struct DictionaryAddEvent<TKey, TValue>(TKey Key, TValue Value);
public readonly record struct DictionaryRemoveEvent<TKey, TValue>(TKey Key, TValue Value);
public readonly record struct DictionaryReplaceEvent<TKey, TValue>(TKey Key, TValue OldValue, TValue NewValue);
public static class ObservableCollectionR3Extensions
{
public static Observable<CollectionAddEvent<T>> ObserveAdd<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
@ -43,6 +51,26 @@ public static class ObservableCollectionR3Extensions
}
}
public static class ObservableDictionaryR3Extensions
{
public static Observable<DictionaryAddEvent<TKey, TValue>> ObserveDictionaryAdd<TKey, TValue>(this IReadOnlyObservableDictionary<TKey, TValue> source,
CancellationToken cancellationToken = default)
{
return new ObservableDictionaryAdd<TKey, TValue>(source, cancellationToken);
}
public static Observable<DictionaryRemoveEvent<TKey, TValue>> ObserveDictionaryRemove<TKey, TValue>(this IReadOnlyObservableDictionary<TKey, TValue> source,
CancellationToken cancellationToken = default)
{
return new ObservableDictionaryRemove<TKey, TValue>(source, cancellationToken);
}
public static Observable<DictionaryReplaceEvent<TKey, TValue>> ObserveDictionaryReplace<TKey, TValue>(this IReadOnlyObservableDictionary<TKey, TValue> source,
CancellationToken cancellationToken = default)
{
return new ObservableDictionaryReplace<TKey, TValue>(source, cancellationToken);
}
}
sealed class ObservableCollectionAdd<T>(IObservableCollection<T> collection, CancellationToken cancellationToken)
: Observable<CollectionAddEvent<T>>
{
@ -225,6 +253,110 @@ sealed class ObservableCollectionCountChanged<T>(IObservableCollection<T> collec
}
}
sealed class ObservableDictionaryAdd<TKey, TValue>(
IReadOnlyObservableDictionary<TKey, TValue> dictionary,
CancellationToken cancellationToken) : Observable<DictionaryAddEvent<TKey, TValue>>
{
protected override IDisposable SubscribeCore(Observer<DictionaryAddEvent<TKey, TValue>> observer)
{
return new _DictionaryCollectionAdd(dictionary, observer, cancellationToken);
}
sealed class _DictionaryCollectionAdd(
IObservableCollection<KeyValuePair<TKey, TValue>> collection,
Observer<DictionaryAddEvent<TKey, TValue>> observer,
CancellationToken cancellationToken) :
ObservableCollectionObserverBase<KeyValuePair<TKey, TValue>, DictionaryAddEvent<TKey, TValue>>(collection,
observer, cancellationToken)
{
protected override void Handler(in NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
{
if (eventArgs.IsSingleItem)
{
observer.OnNext(
new DictionaryAddEvent<TKey, TValue>(eventArgs.NewItem.Key, eventArgs.NewItem.Value));
}
else
{
var i = eventArgs.NewStartingIndex;
foreach (var item in eventArgs.NewItems)
{
observer.OnNext(new DictionaryAddEvent<TKey, TValue>(item.Key, item.Value));
}
}
}
}
}
}
sealed class ObservableDictionaryRemove<TKey, TValue>(
IReadOnlyObservableDictionary<TKey, TValue> dictionary,
CancellationToken cancellationToken) : Observable<DictionaryRemoveEvent<TKey, TValue>>
{
protected override IDisposable SubscribeCore(Observer<DictionaryRemoveEvent<TKey, TValue>> observer)
{
return new _DictionaryCollectionRemove(dictionary, observer, cancellationToken);
}
sealed class _DictionaryCollectionRemove(
IObservableCollection<KeyValuePair<TKey, TValue>> collection,
Observer<DictionaryRemoveEvent<TKey, TValue>> observer,
CancellationToken cancellationToken) :
ObservableCollectionObserverBase<KeyValuePair<TKey, TValue>, DictionaryRemoveEvent<TKey, TValue>>(collection,
observer, cancellationToken)
{
protected override void Handler(in NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
{
if (eventArgs.IsSingleItem)
{
observer.OnNext(
new DictionaryRemoveEvent<TKey, TValue>(eventArgs.OldItem.Key, eventArgs.OldItem.Value));
}
else
{
var i = eventArgs.NewStartingIndex;
foreach (var item in eventArgs.NewItems)
{
observer.OnNext(new DictionaryRemoveEvent<TKey, TValue>(item.Key, item.Value));
}
}
}
}
}
}
sealed class ObservableDictionaryReplace<TKey, TValue>(
IReadOnlyObservableDictionary<TKey, TValue> dictionary,
CancellationToken cancellationToken) : Observable<DictionaryReplaceEvent<TKey, TValue>>
{
protected override IDisposable SubscribeCore(Observer<DictionaryReplaceEvent<TKey, TValue>> observer)
{
return new _DictionaryCollectionReplace(dictionary, observer, cancellationToken);
}
sealed class _DictionaryCollectionReplace(
IObservableCollection<KeyValuePair<TKey, TValue>> collection,
Observer<DictionaryReplaceEvent<TKey, TValue>> observer,
CancellationToken cancellationToken) :
ObservableCollectionObserverBase<KeyValuePair<TKey, TValue>, DictionaryReplaceEvent<TKey, TValue>>(collection,
observer, cancellationToken)
{
protected override void Handler(in NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>> eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Replace)
{
observer.OnNext(new DictionaryReplaceEvent<TKey, TValue>(
eventArgs.NewItem.Key,
eventArgs.OldItem.Value,
eventArgs.NewItem.Value));
}
}
}
}
abstract class ObservableCollectionObserverBase<T, TEvent> : IDisposable
{

View File

@ -15,6 +15,11 @@ namespace ObservableCollections
ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false);
}
public interface IReadOnlyObservableDictionary<TKey, TValue> :
IReadOnlyDictionary<TKey, TValue>, IObservableCollection<KeyValuePair<TKey, TValue>>
{
}
public interface IFreezedCollection<T>
{
ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false);

View File

@ -6,8 +6,8 @@ using System.Diagnostics.CodeAnalysis;
namespace ObservableCollections
{
public sealed partial class ObservableDictionary<TKey, TValue>
: IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IObservableCollection<KeyValuePair<TKey, TValue>>
public sealed partial class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>,
IReadOnlyObservableDictionary<TKey, TValue>
where TKey : notnull
{
readonly Dictionary<TKey, TValue> dictionary;

View File

@ -10,7 +10,7 @@ public class ObservableCollectionExtensionsTest
public void ObserveAdd()
{
var events = new List<CollectionAddEvent<int>>();
var collection = new ObservableList<int>();
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<CollectionAddEvent<int>>();
var result = default(Result?);
var collection = new ObservableList<int>();
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<DictionaryAddEvent<int, string>>();
var dictionary = new ObservableDictionary<int, string>();
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<DictionaryRemoveEvent<int, string>>();
var dictionary = new ObservableDictionary<int,string>
{
{ 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<DictionaryReplaceEvent<int,string>>();
var dictionary = new ObservableDictionary<int, string>()
{
{ 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<int>();
var collection = new ObservableList<int>([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);
}
}