ReadOnly Collections vs Immutable Collections

ReadOnly Collections vs Immutable Collections

In C#, both readonly collections and immutable collections aim to prevent modifications to the collection, but they differ in how they accomplish this and in the guarantees they provide.

In this article I want to have a deep look into their differences as well as other related topics.

Readonly Collections

A readonly collection is a wrapper or proxy around an existing collection that prevents modifications through the collection interface, but the underlying collection can still be modified indirectly if other references to it exist.

Namespace: System.Collections.ObjectModel

Example: ReadOnlyCollection<T>

Important points:

  • A readonly collection can be created using an existing collection (e.g., List<T>, Array).
  • You cannot add, remove, or modify items directly through the readonly collection interface.
  • Underlying collection can still be changed: If you have access to the original collection, you can still modify it, and these changes will be reflected in the readonly version.
  • Readonly collections are primarily a defensive programming measure, allowing a collection to be exposed while ensuring no modifications through that specific reference.

List<int> list = new List<int> { 1, 2, 3 };
ReadOnlyCollection<int> readOnlyList = new ReadOnlyCollection<int>(list);

// Cannot modify via readOnlyList
// readOnlyList.Add(4); // Error: ReadOnlyCollection does not allow modification
// Can modify the original list
list.Add(4);

// readOnlyList will reflect changes from the original list
Console.WriteLine(readOnlyList.Count);  // Output: 4        

ReadOnly Collection Classes

The System.Collections.ObjectModel workspace contains several other read only classes like:

  • ReadOnlyCollection
  • ReadOnlyDictionary
  • ReadOnlyObservableCollection

As the fundamentals are more or less the same, so I focus on ReadOnlyCollection.

You saw in the previous example that by passing a collection to the constructor of ReadOnlyCollection, we can create a read only wrapper of it.

Another way is using the AsReadOnly() method for most collections:

List<string> names=new List<string>(){"Rod", "Jane", "Freddy"};        

Then you can convert like this:

ReadOnlyCollection<string> readOnlyNames=names.AsReadOnly();        

Immutable Collections

An immutable collection is a collection that guarantees that it cannot be modified in any way, once created. Immutable collections do not allow any modifications at all, even if other references to the collection exist.

Namespace: System.Collections.Immutable

Example: ImmutableList<T>, ImmutableArray<T>

Important points:

  • An immutable collection cannot be changed after it has been created.
  • Any "modification" operations, such as adding or removing items, return a new instance of the collection with the changes, leaving the original collection untouched.
  • Immutable collections provide strong guarantees that they will never change, which is useful in multithreading scenarios because they do not require locks for access.
  • Immutable collections are inherently thread-safe because their data cannot be altered.

Example:

using System.Collections.Immutable;
ImmutableList<int> immutableList = ImmutableList.Create(1, 2, 3);

// Modifying operations return a new collection
ImmutableList<int> newList = immutableList.Add(4);

// The original collection remains unchanged
Console.WriteLine(immutableList.Count);  // Output: 3
Console.WriteLine(newList.Count);    // Output: 4        

Immutable Collection Classes

This namespace contains lots of collection classes including both generics and non-generics:

  • ImmutableArray
  • ImmutableArray<T>
  • ImmutableDictioary
  • ImmutableDictioary<T>
  • ImmutableHashSet
  • ImmutableHashSet<T>
  • ImmutableList
  • ImmutableList<T>
  • ImmutableQueue
  • ImmutableQueue <T>
  • ImmutableSortedSet
  • ImmutableSortedSet <T>
  • ImmutableStack
  • ImmutableStack <T>

Differences Summary

In the following table I summerised the key differences between ReadOnly and Immutable collections:


Time complexity

When comparing performance between a normal List<T>, a readonly list (ReadOnlyCollection<T>), and an immutable list (ImmutableList<T>), there are some key performance characteristics to consider. Each collection type is designed for specific use cases, and their performance profiles vary depending on the operations you're performing.

Normal List (Listlt;Tgt;) Operations

  • Access: O(1)
  • Insert at end: Amortized O(1)
  • Insert at specific index: O(n) (due to potential shifting of elements)
  • Remove: O(n) (due to shifting)
  • Memory: Normal list grows dynamically and may allocate more memory than necessary due to internal buffer resizing.

Example:

List<int> list = new List<int> { 1, 2, 3 };
list.Add(4);  // Fast addition
list[1] = 10; // Direct modification        

ReadOnly List (ReadOnlyCollectionlt;Tgt;) Operations

  • Access: O(1) (delegates to the underlying collection)
  • Insert/Remove: Not supported through ReadOnlyCollection<T>, but possible via the underlying collection.
  • Memory: Minimal overhead for the wrapper itself. Since it wraps an existing collection, it does not duplicate data.
  • Creation Cost: O(1) since it only wraps the underlying collection.

Example:

List<int> list = new List<int> { 1, 2, 3 };
ReadOnlyCollection<int> readOnlyList = new ReadOnlyCollection<int>(list);

// Access is fast but no modification is allowed via readOnlyList        

Immutable List (ImmutableListlt;Tgt;) Operations

  • Access: O(1)
  • Insert at end: O(log n) due to structural sharing.
  • Insert at specific index: O(log n) (relatively slow compared to a normal list, but optimized via structural sharing).
  • Remove: O(log n)
  • Memory: Memory-efficient due to structural sharing (doesn’t create a full copy of the original collection on each change).
  • Creation Cost: Higher initial creation cost because it has to build the immutable structure.

Example:

ImmutableList<int> immutableList = ImmutableList.Create(1, 2, 3);
ImmutableList<int> newList = immutableList.Add(4);  // Returns a new list with 4        

Performance Comparison

Read and Access Time

  • List<T> and ReadOnlyCollection<T>: O(1) for access, as both directly index into the list.
  • ImmutableList<T>: O(1) for access as well, due to structural sharing and indexing optimization.

Modification

  • List<T>: Very fast modifications (O(1) for append, O(n) for insertions/removals due to internal shifting).
  • ReadOnlyCollection<T>: Cannot modify directly, so no comparison.
  • ImmutableList<T>: Modifications like adding or removing return a new list, with structural sharing. The performance is O(log n), which is slower than a normal list but optimized for immutability.

Memory Overhead

  • List<T>: May allocate more memory than needed initially to allow for growth, but does not create new collections unless capacity is exceeded.
  • ReadOnlyCollection<T>: Minimal overhead because it just wraps the original list.
  • ImmutableList<T>: Uses structural sharing to minimize memory use when changes occur, but overall, it has a higher memory overhead compared to List<T> because of the cost of maintaining immutability.

Performance Summary Table

In this table I summarised performance comparison between normal list and ReadOnly and Immutable list:


How about using the readonly keyword?

The readonly keyword in C# and the ReadOnlyCollection<T> class both limit modifications to collections, but they work in different ways and have different implications. Let's explore the differences, pros, and cons of using readonly with a collection vs. using ReadOnlyCollection<T>.

readonly Keyword

The readonly keyword in C# is used to ensure that a field (like a collection) can only be assigned a value during its initialization (in the constructor or at the point of declaration). However, it does not prevent modifications to the elements within the collection while the two other collections ReadOnly and Immutable protect the collection from modification. Note to this example:

class Test
{
     public readonly List<int> myList = new List<int> { 1, 2, 3 };
     public void AddItem()
     {
          myList.Add(4);  // Allowed
     } 

     public void ReplaceList()
     {
          // myList = new List<int>();  // Not allowed (compile-time error)
     }
}        

Pros:

  • Prevents reassignment: The reference to the List<int> cannot be changed after initialization, ensuring that the myList always points to the same instance of a list.
  • Allows element modification: You can still modify the elements within the list (e.g., add, remove, or change items).
  • Minimal overhead: Using readonly adds no runtime overhead. It's purely a compile-time restriction.

Cons:

  • Doesn't prevent modifications: The collection itself (i.e., the contents of the list) can still be modified—items can be added, removed, or updated.
  • Only applies to the reference: The readonly keyword restricts only the reassignment of the reference, not the collection's elements.

Usage:

  • Good for collections that should not be replaced, but where modification of the contents is allowed.
  • Not useful if you need to fully protect the collection from any modifications.

The following table may help you to have better understanding about differences between using readonly keyword and ReadOnlyCollection:


Conclusion

  • List<T> is the fastest and most memory-efficient for scenarios where mutability is acceptable and thread safety is not a concern.
  • ReadOnlyCollection<T> is a thin wrapper that prevents modification, but the underlying collection can still change. It has minimal overhead.
  • ImmutableList<T> is slower for modifications, but it’s optimized for immutability, offering better thread-safety without requiring locks. It is ideal for multithreaded applications and functional programming scenarios where immutability is a must.

Choosing between these depends on your specific use case:

  • If you need high performance and can work with mutability, go with List<T>.
  • If you need immutability and thread safety, use ImmutableList<T>.

#csharp #dotnet #collections #readonly #immutable #readonlycollection

要查看或添加评论,请登录

Amir Doosti的更多文章

  • Network Programming in c# - Part 2 (HTTP programming)

    Network Programming in c# - Part 2 (HTTP programming)

    In the previous article I talked about Socket programming. Today, I’m going to cover another type of network…

  • Network Programming in C# - Part 1

    Network Programming in C# - Part 1

    Network programming in C# involves using .NET libraries to communicate between systems over a network.

    2 条评论
  • Locking (Synchronization) in C#

    Locking (Synchronization) in C#

    Concurrency and multithreading are powerful features in modern programming, but they bring challenges, especially in…

    6 条评论
  • Plotting in C# (Part 4 - ScottPlot)

    Plotting in C# (Part 4 - ScottPlot)

    ScottPlot is an open-source, .NET-based charting library designed for creating high-performance, interactive plots in…

  • Plotting in C# (Part 3 - OxyPlot)

    Plotting in C# (Part 3 - OxyPlot)

    OxyPlot is a lightweight, open-source plotting library designed specifically for .NET applications, supporting…

    2 条评论
  • Plotting in C#.Net (Part2 - LiveCharts2)

    Plotting in C#.Net (Part2 - LiveCharts2)

    LiveCharts is a versatile and modern charting library that supports a variety of charts and visualizations with smooth…

  • Plotting in C#.Net (Part 1 - General)

    Plotting in C#.Net (Part 1 - General)

    Plotting is a crucial tool for data analysis, visualization, and communication. There are many reasons why we need to…

    2 条评论
  • Half-Precision floating point in C#

    Half-Precision floating point in C#

    Recently I encountered a problem in a system where we needed to use floating point but we had just two bytes memory for…

    3 条评论
  • Working with Excel files in .Net

    Working with Excel files in .Net

    Using Excel files in software applications is common for several reasons, as they provide a practical and versatile…

  • Deconstructor in C#

    Deconstructor in C#

    I have often heard developers use deconstructor and destructor interchangeably while these words have completely two…

社区洞察

其他会员也浏览了