Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
O

ObservableCollections

> 编程语言
Open source

High performance observable collections and synchronized views, for WPF, Blazor, Unity.

1.0K stars0 likes0 views
WebsiteGitHub

About

High performance observable collections and synchronized views, for WPF, Blazor, Unity.

ObservableCollections

ObservableCollections is a high performance observable collections(ObservableList, ObservableDictionary, ObservableHashSet, ObservableQueue, ObservableStack, ObservableRingBuffer, ObservableFixedSizeRingBuffer) with synchronized views and Observe Extension for R3.

.NET has ObservableCollection, however it has many lacks of features. It based INotifyCollectionChanged, NotifyCollectionChangedEventHandler and NotifyCollectionChangedEventArgs. There are no generics so everything boxed, allocate memory every time. Also NotifyCollectionChangedEventArgs holds all values to IList even if it is single value, this also causes allocations. ObservableCollection has no Range feature so a lot of wastage occurs when adding multiple values, because it is a single value notification. Also, it is not thread-safe is hard to do linkage with the notifier.

ObservableCollections introduces there generics version, NotifyCollectionChangedEventHandler and NotifyCollectionChangedEventArgs, it using latest C# features(in, readonly ref struct, ReadOnlySpan). Also, Sort and Reverse will now be notified.

csharp
public delegate void NotifyCollectionChangedEventHandler(in NotifyCollectionChangedEventArgs e);

public readonly ref struct NotifyCollectionChangedEventArgs
{
    public readonly NotifyCollectionChangedAction Action;
    public readonly bool IsSingleItem;
    public readonly T NewItem;
    public readonly T OldItem;
    public readonly ReadOnlySpan NewItems;
    public readonly ReadOnlySpan OldItems;
    public readonly int NewStartingIndex;
    public readonly int OldStartingIndex;
    public readonly SortOperation SortOperation;
}

Also, use the interface IObservableCollection instead of INotifyCollectionChanged. This is guaranteed to be thread-safe and can produce a View that is fully synchronized with the collection.

csharp
public interface IObservableCollection : IReadOnlyCollection
{
    event NotifyCollectionChangedEventHandler? CollectionChanged;
    object SyncRoot { get; }
    ISynchronizedView CreateView(Func transform);
}

SynchronizedView helps to separate between Model and View (ViewModel). We will use ObservableCollections as the Model and generate SynchronizedView as the View (ViewModel). This architecture can be applied not only to WPF, but also to Blazor, Unity, etc.

The View retains the transformed values. The transform function is called only once during Add, so costly objects that are linked can also be instantiated. Additionally, it has a feature to dynamically show or hide values using filters.

Observable Collections themselves do not implement INotifyCollectionChanged, so they cannot be bound on XAML platforms and the like. However, they can be converted to collections that implement INotifyCollectionChanged using ToNotifyCollectionChanged(), making them suitable for binding.

ObservableCollections has not just a simple list, there are many more data structures. ObservableList, ObservableDictionary, ObservableHashSet, ObservableQueue, ObservableStack, ObservableRingBuffer, ObservableFixedSizeRingBuffer. RingBuffer, especially FixedSizeRingBuffer, can be achieved with efficient performance when there is rotation (e.g., displaying up to 1000 logs, where old ones are deleted when new ones are added). Of course, the AddRange allows for efficient batch processing of large numbers of additions.

If you want to handle each change event with Rx, you can monitor it with the following method by combining it with R3:

csharp
Observable> IObservableCollection.ObserveChanged()
Observable> IObservableCollection.ObserveAdd()
Observable> IObservableCollection.ObserveRemove()
Observable> IObservableCollection.ObserveReplace() 
Observable> IObservableCollection.ObserveMove() 
Observable> IObservableCollection.ObserveReset()
Observable IObservableCollection.ObserveClear()
Observable IObservableCollection.ObserveReverse()
Observable? Comparer)> IObservableCollection.ObserveSort()
Observable IObservableCollection.ObserveCountChanged()

Getting Started

For .NET, use NuGet. For Unity, please read Unity section.

dotnet add package ObservableCollections

create new ObservableList, ObservableDictionary, ObservableHashSet, ObservableQueue, ObservableStack, ObservableRingBuffer, ObservableFixedSizeRingBuffer.

…

While it is possible to manually handle the CollectionChanged event as shown in the example above, you can also create a SynchronizedView as a collection that holds a separate synchronized value.

csharp
var list = new ObservableList();
var view = list.CreateView(x => x.ToString() + "$");

list.Add(10);
list.Add(20);
list.AddRange(new[] { 30, 40, 50 });
list[1] = 60;
list.RemoveAt(3);

foreach (var v in view)
{
    // 10$, 60$, 30$, 50$
    Console.WriteLine(v);
}

// Dispose view is unsubscribe collection changed event.
view.Dispose();

The view can modify the objects being enumerated by attaching a Filter.

csharp
var list = new ObservableList();
using var view = list.CreateView(x => x.ToString() + "$");

list.Add(1);
list.Add(20);
list.AddRange(new[] { 30, 31, 32 });

// attach filter
view.AttachFilter(x => x % 2 == 0);

foreach (var v in view)
{
    // 20$, 30$, 32$
    Console.WriteLine(v);
}

// attach other filter(removed previous filter)
view.AttachFilter(x => x % 2 == 1);

foreach (var v in view)
{
    // 1$, 31$
    Console.WriteLine(v);
}

// Count shows filtered length
Console.WriteLine(view.Count); // 2

The View only allows iteration and Count; it cannot be accessed via an indexer. If indexer access is required, you need to convert it using ToViewList(). Additionally, ToNotifyCollectionChanged() converts it to a synchronized view that implements INotifyCollectionChanged, which is necessary for XAML binding, in addition to providing indexer access.

csharp
// Queue  List Synchronization
var queue = new ObservableQueue();

queue.Enqueue(1);
queue.Enqueue(10);
queue.Enqueue(100);
queue.Enqueue(1000);
queue.Enqueue(10000);

using var view = queue.CreateView(x => x.ToString() + "$");

using var viewList = view.ToViewList();

Console.WriteLine(viewList[2]); // 100$

In the case of ObservableList, calls to Sort and Reverse can also be synchronized with the view.

…

Reactive Extensions with R3

Once the R3 extension package is installed, you can subscribe to ObserveChanged, ObserveAdd, ObserveRemove, ObserveReplace, ObserveMove, ObserveReset, ObserveClear, ObserveReverse, ObserveSort, ObserveCounteChanged events as Rx, allowing you to compose events individually.

dotnet add package ObservableCollections.R3

csharp
using R3;
using ObservableCollections;

var list = new ObservableList();
list.ObserveAdd()
    .Subscribe(x =>
    {
        Console.WriteLine(x);
    });

list.Add(10);
list.Add(20);
list.AddRange(new[] { 10, 20, 30 });

Note that ObserveReset is used to subscribe to Clear, Reverse, and Sort operations in bulk.

In addition to IObservableCollection, there is also a subscription event for ISynchronizedView. In the case of View, ObserveRejected is also added.

Since it is not supported by dotnet/reactive, please use the Rx library R3.

Blazor

In the case of Blazor, StateHasChanged is called and re-enumeration occurs in response to changes in the collection. It's advisable to use the CollectionStateChanged event for this purpose.

…

WPF/Avalonia/WinUI (XAML based UI platforms)

Because of data binding in WPF, it is important that the collection is Observable. ObservableCollections high-performance IObservableCollection cannot be bind to WPF. Call ToNotifyCollectionChanged() to convert it to INotifyCollectionChanged. Also, although ObservableCollections and Views are thread-safe, the WPF UI does not support change notifications from different threads. ToToNotifyCollectionChanged(IColllectionEventDispatcher) allows multi thread changed.

…

SynchronizationContextCollectionEventDispatcher.Current is default implementation of IColllectionEventDispatcher, it is used SynchronizationContext.Current for dispatche ui thread. You can create custom ICollectionEventDispatcher to use custom dispatcher object. For example use WPF Dispatcher:

csharp
public class WpfDispatcherCollection(Dispatcher dispatcher) : ICollectionEventDispatcher
{
    public void Post(CollectionEventDispatcherEventArgs ev)
    {
        dispatcher.InvokeAsync(() =>
        {
            // notify in dispatcher
            ev.Invoke();
        });
    }
}

ToNotifyCollectionChanged() can also be called without going through a View. In this case, it's guaranteed that no filters will be applied, making it faster. If you want to apply filters, please generate a View before calling it. Additionally, ObservableList has a variation called ToNotifyCollectionChangedSlim(). This option doesn't generate a list for the View and shares the actual data, making it the fastest and most memory-efficient option. However, range operations such as AddRange, InsertRange and RemoveRange are not supported by WPF (or Avalonia), so they will throw runtime exceptions.

Views and ToNotifyCollectionChanged are internally connected by events, so they need to be Dispose to release those connections.

Standard Views are readonly. If you want to reflect the results of binding back to the original collection, use CreateWritableView to generate an IWritableSynchronizedView, and then use ToWritableNotifyCollectionChanged to create an INotifyCollectionChanged collection from it.

csharp
public delegate T WritableViewChangedEventHandler(TView newView, T originalValue, ref bool setValue);

public interface IWritableSynchronizedView : ISynchronizedView
{
    NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter);
    NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter, ICollectionEventDispatcher? collectionEventDispatcher);
}

ToWritableNotifyCollectionChanged accepts a delegate called WritableViewChangedEventHandler. newView receives the newly bound value. If setValue is true, it sets a new value to the original collection, triggering notification propagation. The View is also regenerated. If T originalValue is a reference type, you can prevent such propagation by setting setValue to false.

…

Unity

In Unity projects, you can installing ObservableCollections with NugetForUnity. If R3 integration is required, similarly install ObservableCollections.R3 via NuGetForUnity.

In Unity, ObservableCollections and Views are useful as CollectionManagers, since they need to convert T to Prefab for display. Since View objects are generated only once, it's possible to complement GameObjects tied to the collection.

…

Reference

ObservableCollections provides these collections.

…

The IObservableCollection is the base interface for all, containing the CollectionChanged event and the CreateView method.

csharp
public delegate void NotifyCollectionChangedEventHandler(in NotifyCollectionChangedEventArgs e);

public interface IObservableCollection : IReadOnlyCollection
{
    object SyncRoot { get; }
    event NotifyCollectionChangedEventHandler? CollectionChanged;
    ISynchronizedView CreateView(Func transform);
}

The notification event

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

C#

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言