ReactiveList 2.3.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package ReactiveList --version 2.3.2
                    
NuGet\Install-Package ReactiveList -Version 2.3.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ReactiveList" Version="2.3.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ReactiveList" Version="2.3.2" />
                    
Directory.Packages.props
<PackageReference Include="ReactiveList" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ReactiveList --version 2.3.2
                    
#r "nuget: ReactiveList, 2.3.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ReactiveList@2.3.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ReactiveList&version=2.3.2
                    
Install as a Cake Addin
#tool nuget:?package=ReactiveList&version=2.3.2
                    
Install as a Cake Tool

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 INotifyCollectionChanged and INotifyPropertyChanged.
  • 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>, and ICancelable.

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.Immediate inside the list; if you update UI from subscriptions, dispatch to your UI thread.
  • ItemsAdded, ItemsRemoved, ItemsChanged are snapshots of the last change batch only (not cumulative).
  • ReplaceAll(newItems) raises a clear + add-range under the hood. After ReplaceAll:
    • ItemsRemoved contains the cleared items.
    • ItemsAdded contains the new items.
    • ItemsChanged reflects the clear operation (the removed range).
    • A Reset collection change notification is raised.

API quick reference

Interfaces implemented:

  • IList<T>, IList, IReadOnlyList<T>
  • INotifyCollectionChanged, INotifyPropertyChanged
  • ICancelable (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? CollectionChanged
  • event 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 to CurrentItems)
  • 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed

Compatability with Net 6 / 7 and netstandard2.0