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)

C# NameValueCollection comapred with generic Dictionary

In this post, we wiil see:
  • How is NameValueCollection different from Dictionary? 
  • When to use NameValueCollection?
First look at the following example of NameValueCollection.
Example
using System.Collections.Specialized;
public class Program
{
    public static void Main()
    {
        // Creates and initializes a new NameValueCollection.
        NameValueCollection myCol = new NameValueCollection();
        myCol.Add("red", "Rose");
        myCol.Add("green", "Flag");
        myCol.Add("blue", "Sky");
        myCol.Add("blue", "Sky Blue");
        myCol.Add("blue", "Navy Blue");
        myCol.Add(null, "Null key");

        Console.WriteLine("Count: {0}", myCol.Count);
        foreach (string? k in myCol.AllKeys)
        {
            Console.WriteLine("Key:{0} Value:{1}", k, myCol[k]);
        }
        Console.ReadKey();
    }

}
/*
OUTPUT
Count: 3
Key: red Value:Rose
Key:green Value:Flag
Key:blue Value:Sky, Sky Blue, Navy Blue
Key: Value:Null key
*/

The above example code actually perfectly demonstrates the biggest fundamental difference between the two!

Notice the "blue" key is added three times? Instead of throwing an error or overwriting the old value, it combined them into a single, comma-separated string ("Sky, Sky Blue, Navy Blue"). A standard Dictionary would never let you get away with that.

Here is a breakdown of how NameValueCollection differs from Dictionary<TKey, TValue> and exactly when you should use it.

Key Differences

Feature NameValueCollection Dictionary<string, string>
Duplicate Keys Allowed. Automatically joins multiple values under the same key with a comma. Not Allowed. Will throw an exception on .Add() or overwrite the value if using the indexer dict[key] = value.
Type Safety Strictly string only for both keys and values. Generic. Can use any types (e.g., Dictionary<int, Customer>).
Access Methods Can look up values by Key (string) OR by Index (integer). Can only look up values by Key.
Performance Slower. It is an older, non-generic collection (legacy System.Collections.Specialized). Faster. Highly optimized for hash-lookups and avoids boxing/unboxing.
Null Keys Allowed. You can have a null key holding values. Not Allowed. Will throw an ArgumentNullException.

Internal Structure Contrast

Under the hood, NameValueCollection handles duplicate keys by managing an array of strings for each key, which it flattens when you use the default string indexer.

NameValueCollection Structure:

[ "red" ]   ---> [ "Rose" ]

[ "green" ] ---> [ "Flag" ]

[ "blue" ]  ---> [ "Sky", "Sky Blue", "Navy Blue" ]  <-- (Flattens to "Sky,Sky Blue,Navy Blue")

When to use NameValueCollection?

In modern .NET development, Dictionary<TKey, TValue> (or ILookup<TKey, TValue>) is the default choice. However, NameValueCollection is still the right tool for specific scenarios:

1. Handling HTTP Query Strings and Headers

The web inherently allows duplicate keys in URLs (e.g., ?tag=csharp&tag=dotnet&tag=collections). NameValueCollection is uniquely suited for this, which is why legacy ASP.NET used it heavily for Request.QueryString and Request.Headers.

2.Legacy .NET Configuration Files

Older .config files (app.config / web.config) store <appSettings> as a NameValueCollection. If you are dealing with legacy configuration APIs, you'll still run into it.

3. When you need both Key and Ordered Index access

If you need to fetch elements both by their string identifier and by their numerical insertion order (myCol[0]), NameValueCollection supports this out of the box.

Modern Alternatives to Keep in Mind

If you need to map one key to multiple values in modern C# without using an old, string-only collection, consider:

  • Dictionary<string, List<string>>: Gives you strict generic type safety, but you have to write the boilerplate code to initialize the inner list for new keys.
  • ILookup<TKey, TElement>: Created via LINQ (.ToLookup()). Great for multi-value groups, but it is immutable once created.

 

Hot Topics