Why does ListT implement IReadOnlyListT in .NET 4.5?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In .NET 4.5, List<T> was updated to implement IReadOnlyList<T> and IReadOnlyCollection<T>. This seems contradictory — List<T> is clearly mutable, so why does it implement a "read-only" interface? The answer lies in the distinction between read-only views and immutable collections. IReadOnlyList<T> does not make a collection immutable — it provides a read-only contract that consumers can depend on without needing the full mutable interface.
What IReadOnlyList<T> Actually Means
IReadOnlyList<T> provides:
It guarantees that consumers can:
- Read elements by index (
this[int index]) - Get the count (
Count) - Enumerate the collection
It does not guarantee that the collection cannot be modified by someone else. It is a read-only view, not an immutability guarantee.
Why List<T> Implements It
1. Consumer-Side API Design
The primary motivation is allowing methods to accept IReadOnlyList<T> when they only need to read:
2. Covariance
IReadOnlyList<T> is covariant (out T), while IList<T> is invariant. This means:
Covariance is only safe for read-only interfaces because there is no risk of inserting an incompatible type.
3. Liskov Substitution Principle
List<T> legitimately satisfies the IReadOnlyList<T> contract — it does support indexed access and count. The interface says "I can be read," not "I cannot be written." A mutable list is a superset of a read-only list's capabilities, so implementing it is correct.
Read-Only vs Immutable
This distinction is critical:
| Concept | Meaning | .NET Type |
| Read-only | Consumer cannot modify through this interface | IReadOnlyList<T> |
| Immutable | Nobody can modify, ever | ImmutableList<T> (System.Collections.Immutable) |
Practical Usage Patterns
Expose Read-Only, Keep Mutable Internally
Prefer IReadOnlyList<T> in Method Parameters
AsReadOnly() for Defensive Copies
If you need to prevent casting back to List<T>:
Other Types That Implement IReadOnlyList<T>
| Type | Since |
List<T> | .NET 4.5 |
T[] (arrays) | .NET 4.5 |
ReadOnlyCollection<T> | .NET 4.5 |
ImmutableList<T> | NuGet package |
ImmutableArray<T> | NuGet package |
Common Pitfalls
- Assuming
IReadOnlyList<T>means immutable: AList<T>behind anIReadOnlyList<T>reference can still be mutated through the original reference. For true immutability, useImmutableList<T>fromSystem.Collections.Immutable. - Casting back to
List<T>:(List<int>)readOnlyRefworks at runtime if the underlying object is aList<T>. Use.AsReadOnly()to create a wrapper that cannot be cast back. - Thread safety:
IReadOnlyList<T>does not imply thread safety. AList<T>exposed asIReadOnlyList<T>can still be modified concurrently by another thread through the mutable reference. - Performance of AsReadOnly():
ReadOnlyCollection<T>is a thin wrapper with no memory overhead for the elements. The only cost is one extra object allocation. IEnumerable<T>vsIReadOnlyList<T>: PreferIReadOnlyList<T>overIEnumerable<T>when you need indexed access orCountwithout multiple enumeration.IEnumerable<T>may be lazily evaluated and cannot be indexed.
Summary
IReadOnlyList<T>is a read-only view, not an immutability guaranteeList<T>implements it because it legitimately supports read access by index- Use
IReadOnlyList<T>in method parameters to communicate read-only intent - Use covariance:
IReadOnlyList<object> = new List<string>()works becauseout T - For true immutability, use
ImmutableList<T>fromSystem.Collections.Immutable - Use
.AsReadOnly()to prevent consumers from casting back to the mutable type

