Skip to content

Commit

Permalink
Merge pull request #336 from reactiveui/derived-readonly-interfaces
Browse files Browse the repository at this point in the history
Read-only interfaces for derived collections
  • Loading branch information
Paul Betts committed Jul 1, 2013
2 parents 11bb11f + 5cb784d commit f58c66c
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 69 deletions.
4 changes: 2 additions & 2 deletions ReactiveUI.Tests/ReactiveCollectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ public void CreateCollectionWithTimer()
var sched = new TestScheduler();

using (TestUtils.WithScheduler(sched)) {
ReactiveList<string> fixture;
IReactiveDerivedList<string> fixture;

fixture = input.ToObservable(sched).CreateCollection(TimeSpan.FromSeconds(0.5));
sched.AdvanceToMs(1005);
Expand Down Expand Up @@ -831,7 +831,7 @@ public virtual void Test() { }
public class DerivedCollectionTestContainer<TSource, TValue> : DerivedCollectionTestContainer
{
public IEnumerable<TSource> Source { get; set; }
public ReactiveDerivedCollection<TValue> Derived { get; set; }
public IReactiveDerivedList<TValue> Derived { get; set; }
public Func<TSource, TValue> Selector { get; set; }
public Func<TSource, bool> Filter { get; set; }
public IComparer<TValue> Orderer { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion ReactiveUI.Tests/ReactiveCommandTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public void RegisterAsyncFunctionSmokeTest()
{
(new TestScheduler()).With(sched => {
var fixture = new ReactiveCommand();
ReactiveList<int> results;
IReactiveDerivedList<int> results;
results = fixture.RegisterAsync(_ =>
Observable.Return(5).Delay(TimeSpan.FromSeconds(5), sched)).CreateCollection();
Expand Down
180 changes: 127 additions & 53 deletions ReactiveUI/Interfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI
/// will be Covariant which will allow simpler casting between specific and
/// generic changes.
/// </summary>
public interface IObservedChange<TSender, TValue>
public interface IObservedChange<out TSender, out TValue>
{
/// <summary>
/// The object that has raised the change.
Expand Down Expand Up @@ -81,7 +81,7 @@ public interface IReactiveNotifyPropertyChanged : INotifyPropertyChanged, INotif
/// IReactiveNotifyPropertyChanged of TSender is a helper interface that adds
/// typed versions of Changing and Changed.
/// </summary>
public interface IReactiveNotifyPropertyChanged<TSender> : IReactiveNotifyPropertyChanged
public interface IReactiveNotifyPropertyChanged<out TSender> : IReactiveNotifyPropertyChanged
{
new IObservable<IObservedChange<TSender, object>> Changing { get; }
new IObservable<IObservedChange<TSender, object>> Changed { get; }
Expand Down Expand Up @@ -166,78 +166,111 @@ public interface IReactiveCommand : IHandleObservableErrors, IObservable<object>
/// IReactiveNotifyPropertyChanged semantically as "Fire when *anything* in
/// the collection or any of its items have changed, in any way".
/// </summary>
public interface IReactiveCollection : ICollection, INotifyCollectionChanged, INotifyPropertyChanging, INotifyPropertyChanged, IEnableLogger
public interface IReactiveCollection : IReactiveNotifyCollectionChanged, IReactiveNotifyCollectionItemChanged, ICollection, INotifyPropertyChanging, INotifyPropertyChanged, IEnableLogger
{
//
// Collection Tracking
//
}

/// <summary>
/// IReactiveCollection of T is the typed version of IReactiveCollection and
/// adds type-specified versions of Observables
/// </summary>
public interface IReactiveCollection<T> : IReactiveCollection, ICollection<T>, IReactiveNotifyCollectionChanged<T>, IReactiveNotifyCollectionItemChanged<T>
{
}

/// <summary>
/// IReactiveNotifyCollectionItemChanged provides notifications for collection item updates, ie when an object in
/// a collection changes.
/// </summary>
public interface IReactiveNotifyCollectionItemChanged
{
/// <summary>
/// Fires when items are added to the collection, once per item added.
/// Functions that add multiple items such AddRange should fire this
/// multiple times. The object provided is the item that was added.
/// Provides Item Changing notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// </summary>
IObservable<object> ItemsAdded { get; }
IObservable<IObservedChange<object, object>> ItemChanging { get; }

/// <summary>
/// Fires before an item is going to be added to the collection.
/// Provides Item Changed notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// </summary>
IObservable<object> BeforeItemsAdded { get; }
IObservable<IObservedChange<object, object>> ItemChanged { get; }

/// <summary>
/// Fires once an item has been removed from a collection, providing the
/// item that was removed.
/// Enables the ItemChanging and ItemChanged properties; when this is
/// enabled, whenever a property on any object implementing
/// IReactiveNotifyPropertyChanged changes, the change will be
/// rebroadcast through ItemChanging/ItemChanged.
/// </summary>
IObservable<object> ItemsRemoved { get; }
bool ChangeTrackingEnabled { get; set; }
}

/// <summary>
/// IReactiveNotifyCollectionItemChanged of T is the typed version of IReactiveNotifyCollectionItemChanged and
/// adds type-specified versions of Observables
/// </summary>
public interface IReactiveNotifyCollectionItemChanged<out T> : IReactiveNotifyCollectionItemChanged
{
/// <summary>
/// Fires before an item will be removed from a collection, providing
/// the item that will be removed.
/// Provides Item Changing notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// </summary>
IObservable<object> BeforeItemsRemoved { get; }
new IObservable<IObservedChange<T, object>> ItemChanging { get; }

/// <summary>
/// Fires whenever the number of items in a collection has changed,
/// providing the new Count.
/// Provides Item Changed notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// </summary>
IObservable<int> CountChanged { get; }
new IObservable<IObservedChange<T, object>> ItemChanged { get; }
}

/// <summary>
/// IReactiveCollection provides notifications when the contents
/// of collection are changed (items are added/removed/moved).
/// </summary>
public interface IReactiveNotifyCollectionChanged : INotifyCollectionChanged
{
/// <summary>
/// Fires before a collection is about to change, providing the previous
/// Count.
/// Fires when items are added to the collection, once per item added.
/// Functions that add multiple items such AddRange should fire this
/// multiple times. The object provided is the item that was added.
/// </summary>
IObservable<int> CountChanging { get; }
IObservable<object> ItemsAdded { get; }

/// <summary>
/// Fires when a collection becomes or stops being empty.
/// Fires before an item is going to be added to the collection.
/// </summary>
IObservable<bool> IsEmptyChanged { get; }
IObservable<object> BeforeItemsAdded { get; }

//
// Change Tracking
//
/// <summary>
/// Fires once an item has been removed from a collection, providing the
/// item that was removed.
/// </summary>
IObservable<object> ItemsRemoved { get; }

/// <summary>
/// Provides Item Changing notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// Fires before an item will be removed from a collection, providing
/// the item that will be removed.
/// </summary>
IObservable<IObservedChange<object, object>> ItemChanging { get; }
IObservable<object> BeforeItemsRemoved { get; }

/// <summary>
/// Provides Item Changed notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// Fires before an items moves from one position in the collection to
/// another, providing the item(s) to be moved as well as source and destination
/// indices.
/// </summary>
IObservable<IObservedChange<object, object>> ItemChanged { get; }
IObservable<IMoveInfo<object>> BeforeItemsMoved { get; }

/// <summary>
/// Enables the ItemChanging and ItemChanged properties; when this is
/// enabled, whenever a property on any object implementing
/// IReactiveNotifyPropertyChanged changes, the change will be
/// rebroadcast through ItemChanging/ItemChanged.
/// Fires once one or more items moves from one position in the collection to
/// another, providing the item(s) that was moved as well as source and destination
/// indices.
/// </summary>
bool ChangeTrackingEnabled { get; set; }
IObservable<IMoveInfo<object>> ItemsMoved { get; }

/// <summary>
/// This Observable is equivalent to the NotifyCollectionChanged event,
Expand All @@ -263,10 +296,10 @@ public interface IReactiveCollection : ICollection, INotifyCollectionChanged, IN
}

/// <summary>
/// IReactiveCollection of T is the typed version of IReactiveCollection and
/// IReactiveNotifyCollectionChanged of T is the typed version of IReactiveNotifyCollectionChanged and
/// adds type-specified versions of Observables
/// </summary>
public interface IReactiveCollection<T> : ICollection<T>, IReactiveCollection
public interface IReactiveNotifyCollectionChanged<out T> : IReactiveNotifyCollectionChanged
{
/// <summary>
/// Fires when items are added to the collection, once per item added.
Expand All @@ -293,27 +326,68 @@ public interface IReactiveCollection<T> : ICollection<T>, IReactiveCollection
new IObservable<T> BeforeItemsRemoved { get; }

/// <summary>
/// Provides Item Changing notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// Fires before an items moves from one position in the collection to
/// another, providing the item(s) to be moved as well as source and destination
/// indices.
/// </summary>
new IObservable<IObservedChange<T, object>> ItemChanging { get; }
new IObservable<IMoveInfo<T>> BeforeItemsMoved { get; }

/// <summary>
/// Provides Item Changed notifications for any item in collection that
/// implements IReactiveNotifyPropertyChanged. This is only enabled when
/// ChangeTrackingEnabled is set to True.
/// Fires once one or more items moves from one position in the collection to
/// another, providing the item(s) that was moved as well as source and destination
/// indices.
/// </summary>
new IObservable<IObservedChange<T, object>> ItemChanged { get; }
new IObservable<IMoveInfo<T>> ItemsMoved { get; }
}

/// <summary>
/// An IList that reports change notifications
/// IReadOnlyReactiveCollection represents a read-only collection that can notify when its
/// contents are changed (either items are added/removed, or the object
/// itself changes).
///
/// It is important to implement the Changing/Changed from
/// IReactiveNotifyPropertyChanged semantically as "Fire when *anything* in
/// the collection or any of its items have changed, in any way".
/// </summary>
public interface IReadOnlyReactiveCollection<out T> : IReadOnlyCollection<T>, IReactiveNotifyCollectionChanged<T>, IReactiveNotifyCollectionItemChanged<T>, INotifyPropertyChanging, INotifyPropertyChanged, IEnableLogger
{
}

/// <summary>
/// IReactiveList represents a list that can notify when its
/// contents are changed (either items are added/removed, or the object
/// itself changes).
///
/// It is important to implement the Changing/Changed from
/// IReactiveNotifyPropertyChanged semantically as "Fire when *anything* in
/// the collection or any of its items have changed, in any way".
/// </summary>
public interface IReactiveList<T> : IReactiveCollection<T>, IList<T>, IList
{
}

/// <summary>
/// IReactiveList represents a read-only list that can notify when its
/// contents are changed (either items are added/removed, or the object
/// itself changes).
///
/// It is important to implement the Changing/Changed from
/// IReactiveNotifyPropertyChanged semantically as "Fire when *anything* in
/// the collection or any of its items have changed, in any way".
/// </summary>
public interface IReadOnlyReactiveList<out T> : IReadOnlyReactiveCollection<T>, IReadOnlyList<T>
{
}

/// <summary>
/// IReactiveDerivedList repreents a collection whose contents will "follow" another
/// collection; this method is useful for creating ViewModel collections
/// that are automatically updated when the respective Model collection is updated.
/// </summary>
public interface IReactiveDerivedList<T> : IReadOnlyReactiveList<T>, IDisposable
{
}

// NB: This is just a name we can bolt extension methods to
public interface INavigateCommand : IReactiveCommand { }

Expand Down
12 changes: 6 additions & 6 deletions ReactiveUI/ReactiveCollectionMixins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace ReactiveUI
/// It is read-only, and any attempts to change items in the collection will
/// fail.
/// </summary>
public abstract class ReactiveDerivedCollection<TValue> : ReactiveList<TValue>, IDisposable
internal abstract class ReactiveDerivedCollection<TValue> : ReactiveList<TValue>, IReactiveDerivedList<TValue>, IDisposable
{
const string readonlyExceptionMessage = "Derived collections cannot be modified.";

Expand Down Expand Up @@ -176,7 +176,7 @@ public virtual void Dispose(bool disposing) { }
/// It is read-only, and any attempts to change items in the collection will
/// fail.
/// </summary>
public sealed class ReactiveDerivedCollection<TSource, TValue> : ReactiveDerivedCollection<TValue>, IDisposable
internal sealed class ReactiveDerivedCollection<TSource, TValue> : ReactiveDerivedCollection<TValue>, IDisposable
{
readonly IEnumerable<TSource> source;
readonly Func<TSource, TValue> selector;
Expand Down Expand Up @@ -798,7 +798,7 @@ public static class ReactiveCollectionMixins
/// collection no faster than the delay provided.</param>
/// <returns>A new collection which will be populated with the
/// Observable.</returns>
public static ReactiveDerivedCollection<T> CreateCollection<T>(
public static IReactiveDerivedList<T> CreateCollection<T>(
this IObservable<T> fromObservable,
TimeSpan? withDelay = null,
Action<Exception> onError = null)
Expand All @@ -821,7 +821,7 @@ public static ReactiveDerivedCollection<T> CreateCollection<T>(
/// collection no faster than the delay provided.</param>
/// <returns>A new collection which will be populated with the
/// Observable.</returns>
public static ReactiveDerivedCollection<TRet> CreateCollection<T, TRet>(
public static IReactiveDerivedList<TRet> CreateCollection<T, TRet>(
this IObservable<T> fromObservable,
Func<T, TRet> selector,
TimeSpan? withDelay = null)
Expand Down Expand Up @@ -857,7 +857,7 @@ public static class ObservableCollectionMixin
/// <returns>A new collection whose items are equivalent to
/// Collection.Select().Where().OrderBy() and will mirror changes
/// in the initial collection.</returns>
public static ReactiveDerivedCollection<TNew> CreateDerivedCollection<T, TNew, TDontCare>(
public static IReactiveDerivedList<TNew> CreateDerivedCollection<T, TNew, TDontCare>(
this IEnumerable<T> This,
Func<T, TNew> selector,
Func<T, bool> filter = null,
Expand Down Expand Up @@ -895,7 +895,7 @@ public static ReactiveDerivedCollection<TNew> CreateDerivedCollection<T, TNew, T
/// <returns>A new collection whose items are equivalent to
/// Collection.Select().Where().OrderBy() and will mirror changes
/// in the initial collection.</returns>
public static ReactiveDerivedCollection<TNew> CreateDerivedCollection<T, TNew>(
public static IReactiveDerivedList<TNew> CreateDerivedCollection<T, TNew>(
this IEnumerable<T> This,
Func<T, TNew> selector,
Func<T, bool> filter = null,
Expand Down
17 changes: 10 additions & 7 deletions ReactiveUI/ReactiveList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace ReactiveUI
{
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(CollectionDebugView<>))]
public class ReactiveList<T> : IReactiveList<T>
public class ReactiveList<T> : IReactiveList<T>, IReadOnlyReactiveList<T>
{
public event NotifyCollectionChangedEventHandler CollectionChanging;
public event NotifyCollectionChangedEventHandler CollectionChanged;
Expand Down Expand Up @@ -376,13 +376,16 @@ public IDisposable SuppressChangeNotifications()
public IObservable<IObservedChange<T, object>> ItemChanging { get { return _itemChanging.Value; } }
public IObservable<IObservedChange<T, object>> ItemChanged { get { return _itemChanged.Value; } }

IObservable<object> IReactiveCollection.BeforeItemsAdded { get { return BeforeItemsAdded.Select(x => (object) x); } }
IObservable<object> IReactiveCollection.ItemsAdded { get { return ItemsAdded.Select(x => (object) x); } }
IObservable<object> IReactiveNotifyCollectionChanged.BeforeItemsAdded { get { return BeforeItemsAdded.Select(x => (object)x); } }
IObservable<object> IReactiveNotifyCollectionChanged.ItemsAdded { get { return ItemsAdded.Select(x => (object)x); } }

IObservable<object> IReactiveCollection.BeforeItemsRemoved { get { return BeforeItemsRemoved.Select(x => (object) x); } }
IObservable<object> IReactiveCollection.ItemsRemoved { get { return ItemsRemoved.Select(x => (object) x); } }
IObservable<object> IReactiveNotifyCollectionChanged.BeforeItemsRemoved { get { return BeforeItemsRemoved.Select(x => (object)x); } }
IObservable<object> IReactiveNotifyCollectionChanged.ItemsRemoved { get { return ItemsRemoved.Select(x => (object) x); } }

IObservable<IObservedChange<object, object>> IReactiveCollection.ItemChanging {
IObservable<IMoveInfo<object>> IReactiveNotifyCollectionChanged.BeforeItemsMoved { get { return BeforeItemsMoved.Select(x => (IMoveInfo<object>)x); } }
IObservable<IMoveInfo<object>> IReactiveNotifyCollectionChanged.ItemsMoved { get { return ItemsMoved.Select(x => (IMoveInfo<object>)x); } }

IObservable<IObservedChange<object, object>> IReactiveNotifyCollectionItemChanged.ItemChanging {
get {
return _itemChanging.Value.Select(x => (IObservedChange<object, object>) new ObservedChange<object, object>() {
Sender = x.Sender,
Expand All @@ -392,7 +395,7 @@ IObservable<IObservedChange<object, object>> IReactiveCollection.ItemChanging {
}
}

IObservable<IObservedChange<object, object>> IReactiveCollection.ItemChanged {
IObservable<IObservedChange<object, object>> IReactiveNotifyCollectionItemChanged.ItemChanged {
get {
return _itemChanged.Value.Select(x => (IObservedChange<object, object>) new ObservedChange<object, object>() {
Sender = x.Sender,
Expand Down

0 comments on commit f58c66c

Please sign in to comment.