ReactiveList 2.3.2
See the version list below for details.
dotnet add package ReactiveList --version 2.3.2
NuGet\Install-Package ReactiveList -Version 2.3.2
<PackageReference Include="ReactiveList" Version="2.3.2" />
<PackageVersion Include="ReactiveList" Version="2.3.2" />
<PackageReference Include="ReactiveList" />
paket add ReactiveList --version 2.3.2
#r "nuget: ReactiveList, 2.3.2"
#:package ReactiveList@2.3.2
#addin nuget:?package=ReactiveList&version=2.3.2
#tool nuget:?package=ReactiveList&version=2.3.2
ReactiveList
A lightweight reactive list with fine-grained change tracking built on DynamicData and System.Reactive.
The list exposes reactive streams for what changed (Added/Removed/Changed), and a stream of the current items snapshot, while also implementing common list interfaces and change notifications for easy UI binding.
Targets: .NET Standard 2.0, .NET 8, .NET 9, .NET 10
Why use ReactiveList?
- Reactive: Subscribe to changes as they happen.
- UI-friendly: Implements
INotifyCollectionChangedandINotifyPropertyChanged. - Easy binding: Exposes a
ReadOnlyObservableCollection<T>for the current items. - Granular change info: Access the last Added/Removed/Changed batch via collections and/or observables.
- Familiar API: Implements
IList<T>,IList,IReadOnlyList<T>, andICancelable.
Getting started
Create a reactive list and subscribe to changes.
using CP.Reactive;
var list = new ReactiveList<string>();
// React to items added in the last change
var addedSub = list.Added.Subscribe(added =>
{
Console.WriteLine($"Added: {string.Join(", ", added)}");
});
// React to items removed in the last change
var removedSub = list.Removed.Subscribe(removed =>
{
Console.WriteLine($"Removed: {string.Join(", ", removed)}");
});
// React to any items changed (add/remove/replace) in the last change
var changedSub = list.Changed.Subscribe(changed =>
{
Console.WriteLine($"Changed: {string.Join(", ", changed)}");
});
// Observe the current items (snapshot) whenever the count changes
var currentSub = list.CurrentItems.Subscribe(items =>
{
Console.WriteLine($"Current: [{string.Join(", ", items)}]");
});
// Work with the list just like a normal list
list.Add("one");
list.AddRange(["two", "three"]);
list.Insert(1, "two-point-five");
list.Remove("two");
list.RemoveAt(0);
list.RemoveRange(0, 1);
// Replace all items in a single operation
list.ReplaceAll(["a", "b", "c"]);
// Update an item (replace a specific value)
list.Update("b", "B");
// Access the read-only view of items (UI binding friendly)
var items = list.Items; // ReadOnlyObservableCollection<string>
// Cleanup
addedSub.Dispose();
removedSub.Dispose();
changedSub.Dispose();
currentSub.Dispose();
list.Dispose();
WPF/WinUI binding
Because ReactiveList<T> implements INotifyCollectionChanged, INotifyPropertyChanged, and IEnumerable<T>, you can bind directly.
public sealed class MyViewModel
{
public IReactiveList<string> Items { get; } = new ReactiveList<string>(new[] { "One", "Two" });
}
<ListBox ItemsSource="{Binding Items}" />
Alternatively, bind to Items for an explicit ReadOnlyObservableCollection<T>:
<ListBox ItemsSource="{Binding Items.Items}" />
Behavior notes
- Observables run on
Scheduler.Immediateinside the list; if you update UI from subscriptions, dispatch to your UI thread. ItemsAdded,ItemsRemoved,ItemsChangedare snapshots of the last change batch only (not cumulative).ReplaceAll(newItems)raises a clear + add-range under the hood. AfterReplaceAll:ItemsRemovedcontains the cleared items.ItemsAddedcontains the new items.ItemsChangedreflects the clear operation (the removed range).- A
Resetcollection change notification is raised.
API quick reference
Interfaces implemented:
IList<T>,IList,IReadOnlyList<T>INotifyCollectionChanged,INotifyPropertyChangedICancelable(IsDisposed)
Properties:
ReadOnlyObservableCollection<T> Items— current items for binding.ReadOnlyObservableCollection<T> ItemsAdded— items added in the last change.ReadOnlyObservableCollection<T> ItemsRemoved— items removed in the last change.ReadOnlyObservableCollection<T> ItemsChanged— items changed (add/remove/replace) in the last change.IObservable<IEnumerable<T>> Added— stream of items added each change.IObservable<IEnumerable<T>> Removed— stream of items removed each change.IObservable<IEnumerable<T>> Changed— stream of items changed each change.IObservable<IEnumerable<T>> CurrentItems— current items snapshot on count changes.int Count,bool IsDisposed.
Indexers:
T this[int index] { get; set; }object? IList.this[int index] { get; set; }
Events:
event NotifyCollectionChangedEventHandler? CollectionChangedevent PropertyChangedEventHandler? PropertyChanged
Operations:
void Add(T item)void AddRange(IEnumerable<T> items)void Insert(int index, T item)void InsertRange(int index, IEnumerable<T> items)bool Remove(T item)/void Remove(IEnumerable<T> items)/void RemoveAt(int index)/void RemoveRange(int index, int count)void Clear()void ReplaceAll(IEnumerable<T> items)void Update(T item, T newValue)int IndexOf(T item)/bool Contains(T item)void CopyTo(T[] array, int arrayIndex)/void CopyTo(Array array, int index)IDisposable Subscribe(IObserver<IEnumerable<T>> observer)(subscribes toCurrentItems)void Dispose()
Examples
ReplaceAll semantics:
var list = new ReactiveList<string>(["one", "two"]);
// At this point:
// list.ItemsAdded.Count == 2
// list.ItemsChanged.Count == 2
// list.ItemsRemoved.Count == 0
list.ReplaceAll(["three", "four", "five"]);
// After ReplaceAll:
// list.ItemsAdded.Count == 3 // new items
// list.ItemsRemoved.Count == 2 // old items cleared
// list.ItemsChanged.Count == 2 // clear change set (removed range)
Subscribe to snapshots of the current items:
var list = new ReactiveList<int>();
list.CurrentItems.Subscribe(items =>
{
// Runs when Count changes
var sum = items.Sum();
Console.WriteLine($"Sum: {sum}");
});
list.AddRange([1, 2, 3]); // triggers CurrentItems
list.Remove(2); // triggers CurrentItems
Reactive2DList
Reactive2DList<T> is a reactive list of reactive lists: Reactive2DList<T> : ReactiveList<ReactiveList<T>>.
Use it for grid- or table-like data structures where rows are dynamic and each row's items are also dynamic.
All ReactiveList behavior applies at the outer level (rows). Each inner row is its own ReactiveList<T>.
Constructing a Reactive2DList
using CP.Reactive;
// Empty 2D list
var grid = new Reactive2DList<int>();
// With rows from IEnumerable<IEnumerable<T>>
var grid2 = new Reactive2DList<int>(new[]
{
new[] { 1, 2, 3 },
new[] { 4, 5 },
});
// With existing reactive rows
var rowA = new ReactiveList<int>(new[] { 10, 11 });
var rowB = new ReactiveList<int>(new[] { 12 });
var grid3 = new Reactive2DList<int>(new[] { rowA, rowB });
// With IEnumerable<T>: creates a grid with one single-element row per item
var grid4 = new Reactive2DList<int>(new[] { 7, 8, 9 });
// grid4 == [ [7], [8], [9] ]
// With a single row
var grid5 = new Reactive2DList<int>(new ReactiveList<int>(new[] { 1, 2, 3 }));
// With a single value (one row, one item)
var grid6 = new Reactive2DList<int>(42);
Adding rows
var grid = new Reactive2DList<string>();
// Add multiple rows (each inner IEnumerable becomes a new row)
grid.AddRange(new[]
{
new[] { "a1", "a2" },
new[] { "b1" },
});
// Add one row per item (each item becomes a single-element row)
grid.AddRange(new[] { "x", "y" }); // => rows: [ ["x"], ["y"] ]
Inserting
var grid = new Reactive2DList<string>(new[]
{
new[] { "r0c0", "r0c1" },
new[] { "r1c0" },
});
// Insert a new row at index 1
grid.Insert(1, new[] { "new", "row" });
// Insert a single-element row at index 0
grid.Insert(0, "solo");
// Insert items into an existing row (index 2), starting at innerIndex 1
grid.Insert(2, new[] { "mid1", "mid2" }, innerIndex: 1);
Observing changes
You can subscribe to row-level changes on the outer list, and item-level changes on each inner row.
var grid = new Reactive2DList<string>();
grid.Added.Subscribe(rows =>
{
Console.WriteLine($"Rows added: {rows.Count()}");
// Subscribe to each newly added row's changes
foreach (var row in rows)
{
row.Added.Subscribe(items => Console.WriteLine($"Row added items: {string.Join(", ", items)}"));
row.Removed.Subscribe(items => Console.WriteLine($"Row removed items: {string.Join(", ", items)}"));
row.Changed.Subscribe(items => Console.WriteLine($"Row changed items: {string.Join(", ", items)}"));
}
});
// Add a row and then modify it
var row0 = new ReactiveList<string>(new[] { "a", "b" });
grid.Add(row0);
row0.Add("c");
row0.Remove("a");
Binding (nested ItemsControl)
<ItemsControl ItemsSource="{Binding Grid}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Building locally
- Open the solution and build. Projects target
netstandard2.0(library) and modern .NET versions for tests/apps. - Dependencies:
DynamicData,System.Reactive.
License
MIT
ReactiveList - Empowering Industrial Automation with Reactive Technology ⚡🏭
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- DynamicData (>= 9.4.1)
- System.Reactive (>= 6.1.0)
-
net10.0
- DynamicData (>= 9.4.1)
- System.Reactive (>= 6.1.0)
-
net8.0
- DynamicData (>= 9.4.1)
- System.Reactive (>= 6.1.0)
-
net9.0
- DynamicData (>= 9.4.1)
- System.Reactive (>= 6.1.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ReactiveList:
| Package | Downloads |
|---|---|
|
CrissCross.WPF.UI
A Reactive Navigation Framework for ReactiveUI |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.4 | 104 | 2/5/2026 |
| 4.0.2 | 95 | 2/4/2026 |
| 3.0.5 | 128 | 1/13/2026 |
| 3.0.2 | 114 | 1/11/2026 |
| 2.4.5 | 273 | 12/7/2025 |
| 2.4.4 | 233 | 12/7/2025 |
| 2.3.2 | 326 | 10/6/2025 |
| 2.2.1 | 610 | 6/12/2025 |
| 2.2.0 | 301 | 1/28/2025 |
| 2.1.0 | 369 | 10/13/2024 |
| 2.0.2 | 360 | 5/13/2024 |
| 2.0.1 | 207 | 5/13/2024 |
| 2.0.0 | 192 | 5/2/2024 |
| 1.2.0 | 285 | 4/26/2024 |
| 1.1.3 | 395 | 1/29/2024 |
| 1.1.2 | 216 | 1/29/2024 |
| 1.1.1 | 275 | 12/26/2023 |
| 1.0.1 | 235 | 10/7/2023 |
Compatability with Net 6 / 7 and netstandard2.0