Wednesday, July 1, 2026

C# generic ObservableCollection Best Practices

In this post, you see how to work with ObservableCollection<T> in C#.

In C#, an ObservableCollection<T> is a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. It is the absolute go-to collection in data-binding scenarios (like WPF, MAUI, WinUI, and Blazor) because it automatically keeps the UI in sync with your underlying data.

Here is a complete guide on hoftw to work with it.

Why Use ObservableCollection<T>?

If you use a standard List<T> and bind it to a UI element (like a ListView), adding an item to the list won't change what you see on the screen. The UI has no way of knowing the list changed.

ObservableCollection<T> implements the INotifyCollectionChanged interface. Whenever the collection is modified, it fires an event that tells the UI, "Hey, I just changed! Redraw yourself."

Basic Setup and Usage

To use it, you need to import the System.Collections.ObjectModel namespace.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
class Program
{
    static void Main()
    {
        // 1. Initialization
        ObservableCollection<string> superheroes = new ObservableCollection<string>()
        {
            "Batman",
            "Superman"
        };
        // 2. Subscribe to changes (Optional: Usually the UI does this automatically)
        superheroes.CollectionChanged += Superheroes_CollectionChanged;
        // 3. Modifying the collection triggers the event
        superheroes.Add("Wonder Woman");
        superheroes.Remove("Superman");
    }
    // Event handler that fires every time the collection changes
    private static void Superheroes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        Console.WriteLine($"Collection changed! Action: {e.Action}");
        if (e.NewItems != null)
        {
            foreach (var item in e.NewItems)
                Console.WriteLine($"Added: {item}");
        }
        if (e.OldItems != null)
        {
            foreach (var item in e.OldItems)
                Console.WriteLine($"Removed: {item}");
        }
    }
} 
Notes
  1. In many ways, working with ObservableCollection<T> is identical to working with List<T>, given that both of these classes implement the same core interfaces. What makes the ObservableCollection<T> class unique is that this class supports an event named CollectionChanged. This event will fire whenever a new item is inserted, a current item is removed (or relocated), or the entire collection is modified.
  2. CollectionChanged event is defined in terms of a delegate, NotifyCollectionChangedEventHandler. This delegate can call any method that takes an object as the first parameter and takes a NotifyCollectionChangedEventArgs as the second.
  3. The NotifyCollectionChangedEventArgs parameter defines two important properties, OldItems and NewItems, which give you a list of items that were currently in the collection before the event fired and the new items that were involved in the change.
  4. The NotifyCollectionChangedEventArgs parameter defines Action property, which returns enum type NotifyCollectionChangedAction value (Add = 0, Remove = 1, Replace = 2, Move = 3, Reset = 4).

Crucial Gotchas & Best Practices

While ObservableCollection<T> is incredibly useful, it has a few quirks that can trip you up if you aren't careful.

⚠️ Gotcha 1: It only tracks Collection changes, not Property changes

If you modify a property of an item inside the collection, the collection does not fire a notification.

  • Triggers UI Update: Add(new User { Name = "Alice" });
  • Does NOT Trigger UI Update: myCollection[0].Name = "Bob";

The Fix: The objects inside your collection must implement the INotifyPropertyChanged interface.

⚠️ Gotcha 2: UI Thread Limitations

By default, if you try to modify an ObservableCollection<T> from a background thread while it is data-bound to a UI element, your app will crash with a cross-thread exception.

The Fix: In WPF, you can use BindingOperations.EnableCollectionSynchronization to allow multi-threaded access, or ensure you marshal modifications back to the main thread using something like MainThread.BeginInvokeOnMainThread (in MAUI) or the UI dispatcher.

⚠️ Gotcha 3: Performance with Bulk Updates

If you add 1,000 items to an ObservableCollection<T> using a foreach loop, it will fire the CollectionChanged event 1,000 times. This can cause severe UI stuttering.

The Fix: Create a custom class that inherits from ObservableCollection<T> and implements an AddRange method that suppresses notifications until all items are added:

public class RangeObservableCollection<T> : ObservableCollection<T>
{
    public void AddRange(IEnumerable<T> list)
    {
        // Suppress notification logic or temporarily detach event,
        // add items, and fire a single Reset action notification.
        foreach (var item in list)
        {
            this.Items.Add(item);
        }
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

Summary Comparison

Feature List<T> ObservableCollection<T>
Best Used For Backend logic, data processing, loops UI Data Binding
Performance Faster (less overhead) Slower (fires events on changes)
Notifies UI on Add/Remove? No Yes
Notifies UI on Internal Property Change? No No (Requires INotifyPropertyChanged on the T item)

No comments:

Post a Comment

Hot Topics