Merge pull request #45 from TORISOUP/add_readonlyObservableDictionary
Add IReadOnlyObservableDictionary interface and ObservableDictionaryR3Extensions
This commit is contained in:
commit
7e139dc57a
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user