ReadOnlyCollection and ReadOnlyDictionary in C
In C#, ReadOnlyCollection<T> and
ReadOnlyDictionary<TKey, TValue> provide read-only
wrappers around mutable collections to prevent unauthorized
modifications while exposing internal data. This article explains what
these classes are, why and when to use them, how they differ from
immutable collections, and provides straightforward code examples
demonstrating their implementation.
What Are ReadOnlyCollection and ReadOnlyDictionary?
Located in the System.Collections.ObjectModel namespace,
ReadOnlyCollection<T> and
ReadOnlyDictionary<TKey, TValue> are wrapper classes
that implement read-only interfaces
(IReadOnlyList<T>,
IReadOnlyCollection<T>, and
IReadOnlyDictionary<TKey, TValue>).
Rather than copying data into a new data structure, these wrappers
hold a reference to an existing mutable collection
(IList<T> or
IDictionary<TKey, TValue>). They expose members for
reading data—such as indexers, count properties, and enumeration—while
omitting or explicitly disabling methods that modify state, such as
Add, Remove, or Clear.
Primary Use Cases
1. Encapsulation and Data Protection
The primary use case is exposing internal state from a class without allowing consumers to alter the underlying collection. This preserves the integrity of your domain models and services.
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class Order
{
private readonly List<string> _items = new();
// Expose as ReadOnlyCollection
public ReadOnlyCollection<string> Items { get; }
public Order()
{
Items = new ReadOnlyCollection<string>(_items);
// Alternatively using LINQ / extension method:
// Items = _items.AsReadOnly();
}
public void AddItem(string item)
{
_items.Add(item);
}
}Consumers can read the Items property and iterate
through it, but they cannot invoke .Add() or
.Remove() directly on the property.
2. Communicating API Intent
Using these types in method signatures or property definitions
explicitly signals to other developers that the collection is intended
for reading only. This eliminates ambiguity compared to exposing
IList<T> or List<T>.
3. Exposing Key-Value Lookups Safely
ReadOnlyDictionary<TKey, TValue> serves the same
purpose for dictionary structures. It allows consumers to query values
by key without granting the ability to insert, overwrite, or remove
entries.
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class ConfigurationManager
{
private readonly Dictionary<string, string> _settings = new();
public ReadOnlyDictionary<string, string> Settings { get; }
public ConfigurationManager()
{
Settings = new ReadOnlyDictionary<string, string>(_settings);
// Or using extension method:
// Settings = _settings.AsReadOnly();
}
public void SetSetting(string key, string value)
{
_settings[key] = value;
}
}Key Characteristics and Behaviors
- Live View of Data: Because these classes wrap existing instances rather than creating defensive copies, any changes made to the underlying mutable collection internally will immediately reflect in the read-only wrapper.
- Low Memory Overhead: Creating a wrapper does not duplicate the elements in memory, making it a lightweight operation compared to cloning or copying collections.
- Not Intrinsically Thread-Safe: If the underlying collection is modified on one thread while another thread is reading from the read-only view, thread safety issues can still occur.
ReadOnly vs. Immutable Collections
It is important not to confuse
ReadOnlyCollection<T> with types from the
System.Collections.Immutable namespace (such as
ImmutableList<T> or
ImmutableDictionary<TKey, TValue>):
- ReadOnly Collections: Act as a "window" or view over a mutable collection. The data can change if the underlying collection is modified.
- Immutable Collections: Cannot be modified once created. Any operation that appears to mutate an immutable collection returns an entirely new instance, leaving the original intact.