.NET 4.0
read-only list
unmodifiable list
C#
software development

Read-only list or unmodifiable list in .NET 4.0

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

In .NET 4.0, there is an important difference between a list that cannot be modified through one reference and a list that is truly immutable. The framework gives you solid tools for exposing read-only views, but it does not include the later immutable collections that many developers know from newer libraries. That means the right answer depends on whether you need a view, a snapshot, or real immutability.

What .NET 4.0 Actually Offers

The usual built-in answer is ReadOnlyCollection<T> or List<T>.AsReadOnly(). These wrap a mutable list so callers cannot mutate it through the returned object.

csharp
1using System;
2using System.Collections.Generic;
3using System.Collections.ObjectModel;
4
5class Program
6{
7    static void Main()
8    {
9        var numbers = new List<int> { 1, 2, 3 };
10        ReadOnlyCollection<int> readOnly = numbers.AsReadOnly();
11
12        Console.WriteLine(readOnly[0]);
13        Console.WriteLine(readOnly.Count);
14    }
15}

This is useful when outside code should inspect the collection but should not be allowed to call Add, Remove, or Clear on it.

The Important Limitation

ReadOnlyCollection<T> is not true immutability. It is a read-only view over an underlying mutable list.

csharp
1using System;
2using System.Collections.Generic;
3
4class Program
5{
6    static void Main()
7    {
8        var source = new List<string> { "a", "b" };
9        var readOnly = source.AsReadOnly();
10
11        source.Add("c");
12        Console.WriteLine(readOnly.Count); // prints 3
13    }
14}

If the backing list changes, the read-only wrapper reflects those changes immediately. That is why it is more accurate to call it unmodifiable through that reference rather than deeply immutable.

A Good Encapsulation Pattern

A common and effective design is to keep the mutable list private and expose only a read-only wrapper:

csharp
1using System.Collections.Generic;
2using System.Collections.ObjectModel;
3
4public class OrderBook
5{
6    private readonly List<string> _orders = new List<string>();
7
8    public ReadOnlyCollection<string> Orders
9    {
10        get { return _orders.AsReadOnly(); }
11    }
12
13    public void Add(string order)
14    {
15        _orders.Add(order);
16    }
17}

This does not make the internal list immutable, but it does prevent callers from mutating it directly. In many APIs, that is the real requirement.

Returning IEnumerable<T> Is Not the Same Thing

Some code returns IEnumerable<T> to hide mutation methods:

csharp
1using System.Collections.Generic;
2
3public class Catalog
4{
5    private readonly List<string> _items = new List<string>();
6
7    public IEnumerable<string> Items
8    {
9        get { return _items; }
10    }
11}

This narrows the surface area, but it is not the same as returning an unmodifiable list. It also does not communicate list semantics such as indexing or count.

If consumers really need list-like behavior, ReadOnlyCollection<T> is clearer.

Returning a Snapshot Instead of a View

If you need stronger protection, create a defensive copy before wrapping it:

csharp
1using System.Collections.Generic;
2using System.Collections.ObjectModel;
3
4public static class SnapshotFactory
5{
6    public static ReadOnlyCollection<int> CreateSnapshot(List<int> values)
7    {
8        return new List<int>(values).AsReadOnly();
9    }
10}

Now later changes to the original list do not affect the returned collection. This uses more memory, but it behaves like a stable snapshot rather than a live view.

What .NET 4.0 Does Not Include

Modern immutable collections such as ImmutableList<T> arrived later through separate libraries and newer platform support. In plain .NET 4.0, you should not expect a built-in persistent immutable collection with copy-on-write behavior.

So the design choices in .NET 4.0 are usually:

  • expose a read-only wrapper over a private list
  • return a defensive copy when snapshot behavior matters
  • implement stricter custom logic if the domain really requires it

Common Pitfalls

The biggest mistake is assuming AsReadOnly() freezes the underlying list. It does not.

Another mistake is exposing the original List<T> somewhere else in the API. Once the mutable reference escapes, the read-only wrapper no longer protects much.

People also return IEnumerable<T> when consumers really need list semantics. Hiding methods is not the same as expressing the right contract.

Finally, do not promise immutability unless you really provide it. In .NET 4.0, a read-only wrapper and a truly immutable collection are different things.

Summary

  • In .NET 4.0, ReadOnlyCollection<T> and AsReadOnly() provide read-only views, not true immutability.
  • A read-only wrapper still reflects changes made to the backing list.
  • Keep the mutable list private if you want callers to see an unmodifiable collection.
  • Return a defensive copy when you need snapshot behavior.
  • Do not confuse interface restriction with actual immutable data.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.