Sunday, June 21, 2026

C# Example of Implementing IReadOnlyCollection generic collection

Step1. Create a class that implements IReadOnlyCollection<T>
lass MyReadOnlyCollection<T> : IReadOnlyCollection<T>
{
    private readonly T[] _items; // Added 'readonly' for safety

    public MyReadOnlyCollection(T[] items)
    {
        // Prevent null reference issues
        _items = items ?? throw new ArgumentNullException(nameof(items));
    }

    public int Count => _items.Length;

    public IEnumerator<T> GetEnumerator()
    {
        return new ReadOnlyCollectionEnumerator<T>(_items);

        // Pro-tip: If you didn't want to build a custom enumerator, you could just do:
        // return ((IEnumerable<T>)_items).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator(); // Best practice: call the generic version to avoid duplication
    }
}
Step2. Create a class that implements IEnumerator<T>
class ReadOnlyCollectionEnumerator<T> : IEnumerator<T>
{
    private readonly T[] _items;
    private int _position = -1;

    public ReadOnlyCollectionEnumerator(T[] items)
    {
        _items = items;
    }

    public T Current
    {
        get
        {
            if (_position < 0 || _position >= _items.Length)
            {
                throw new InvalidOperationException(
                    "Enumeration has either not started or has already finished.");
            }
            return _items[_position];
        }
    }

    object IEnumerator.Current => this.Current;

    public void Dispose()
    {
        // Correctly left empty since arrays don't have unmanaged resources
    }

    public bool MoveNext()
    {
        // Increment first, then check if we are within the boundaries
        if (_position < _items.Length - 1)
        {
            _position++;
            return true;
        }
        return false;
    }

    public void Reset()
    {
        _position = -1;
    }
}
Step3. Create a class that consumes MyReadOnlyCollection<T>
lass Program
{
    static void Main(string[] args)
    {
        // Testing the collection
        string[] colorArray = { "Red", "Green", "Blue" };
        MyReadOnlyCollection<string> colors = new MyReadOnlyCollection<string>(colorArray);

        foreach (var color in colors)
        {
            Console.WriteLine(color);
        }
        Console.WriteLine("Total colors: " + colors.Count);
    }
}

C# When to use IReadOnlyCollection generic collection

You should use IReadOnlyCollection<T> when you want to expose a collection that can be counted, but you want to explicitly signal to the consumer that they are not allowed to modify the contents (i.e., no adding, removing, or clearing elements).

It acts as a lightweight, read-only wrapper around a data structure, providing only two guarantees:
  1. You can iterate over it (IEnumerable).
  2. You can ask for its size instantly without iterating (Count).
Key Scenarios for IReadOnlyCollection<T>
1. Public Properties in Classes (Encapsulation)
If your class maintains an internal list, you should never expose it directly as a List<T>. If you do, external code can modify your class's internal state without its knowledge.
public class Order
{
    // Internal state can be modified freely inside the class
    private readonly List<OrderItem> _items = new();

    // External code can see the items and count them, but cannot add/remove them
    public IReadOnlyCollection<OrderItem> Items => _items; 
}
2. When IEnumerable<T> is Too Limiting
IEnumerable<T> is great for lazy evaluation (like LINQ queries), but it doesn't know its own size. If a developer wants to know how many items are in an IEnumerable<T>, they have to call the .Count() extension method, which forces the CPU to loop through the entire collection to count them O(1) time complexity).

By using IReadOnlyCollection<T>, you provide a Count property that is a direct lookup O(1) time complexity), while still keeping the collection read-only.

The Collection Interface Hierarchy
To understand exactly where IReadOnlyCollection<T> fits, it helps to see the progression of collection power in .NET:
  1. IEnumerable<T> can be looped through using foreach.
  2. IReadOnlyCollection<T> can be looped through, and you know how many items you have instantly.
  3. IReadOnlyList<T> can be looped through, you know the size, and you can access the elements by index (e.g., list[3]).
  4. ICollection<T> / IList<T> provides Full access. You can loop, read, index, add, delete, and clear.
Summary Checklist: When to choose it?

Use this Interface... If you need the consumer to...
IEnumerable<T> Stream data lazily (like from a database) or just loop through elements sequentially.
IReadOnlyCollection<T> Safely view a fixed snapshot of a collection and check its size, without indexing or modifying it.
IReadOnlyList<T> View a collection safely, check its size, and look up items via random access index (e.g., items[i]).
List<T> / ICollection<T> Have full permission to add, remove, and mutate the underlying data structure.

Important Caveat: IReadOnlyCollection<T> means the collection structure is read-only (you can't add or remove items). However, if T is a mutable class object, a developer can still change the properties of an item inside the collection (e.g., collection.First().Status = "Changed"). It does not make the underlying objects immutable!

Hot Topics