ICollection

3 min read
Foundational2 min read
Rapid overview

ICollection

TL;DR

ICollection<T> extends IEnumerable<T> with an O(1) Count property plus Add, Remove, Clear, and Contains, so it's the right contract when callers genuinely need a size check or mutation without caring about indexed access. Reach for it instead of IEnumerable<T> to avoid .Count() re-enumerating the whole sequence, but prefer IReadOnlyCollection<T> when you don't intend to expose Add/Remove.

How it works

ICollection — Countable, Modifiable Collections

"Use ICollection when you need Count without enumeration, and callers may Add/Remove items."

❌ Bad example:

public IEnumerable<Position> GetPositions()
{
    return _positions.Where(p => p.IsOpen);
}

// Caller
public void ValidatePositions(IEnumerable<Position> positions)
{
    int count = positions.Count(); // enumerates entire sequence
    if (count > MaxPositions)
        throw new InvalidOperationException("Too many positions");
}

Using .Count() on IEnumerable enumerates the whole sequence—inefficient for validation.

âś… Good example:

public ICollection<Position> GetPositions()
{
    return _positions.Where(p => p.IsOpen).ToList();
}

// Caller
public void ValidatePositions(ICollection<Position> positions)
{
    if (positions.Count > MaxPositions) // O(1) property access
        throw new InvalidOperationException("Too many positions");
}

👉 ICollection.Count is a property (O(1) for most collections), not an enumeration.

🔥 When Add/Remove matters:

public interface IOrderQueue
{
    ICollection<Order> PendingOrders { get; }
}

public class OrderProcessor
{
    private readonly IOrderQueue _queue;

    public void CancelOrder(int orderId)
    {
        var order = _queue.PendingOrders.FirstOrDefault(o => o.Id == orderId);
        if (order != null)
            _queue.PendingOrders.Remove(order); // mutation supported
    }
}

👉 Callers can modify the collection via Add/Remove without exposing concrete type.

đź’ˇ In trading systems:

  • Use ICollection for position snapshots where count validation is critical.
  • Expose ICollection when callers need to add/remove pending orders or alerts.
  • Prefer IEnumerable if Count isn't needed—keeps API minimal.

Quick recall Q&A

Q: What's the difference between IEnumerable and ICollection? A: ICollection extends IEnumerable and adds Count, Add(), Remove(), Clear(), Contains(), and CopyTo(). Use ICollection when these operations are needed.
Q: Does ICollection guarantee O(1) Count? A: Not strictly, but most implementations (List, HashSet) cache Count. LinkedList also has O(1) Count. IEnumerable.Count() enumerates everything.
Q: Should I return ICollection from methods? A: Only if callers legitimately need Count or Add/Remove. Otherwise, IEnumerable is preferable—narrower contract, better encapsulation.
Q: Can I expose ICollection without allowing modification? A: Use IReadOnlyCollection instead. It provides Count but no Add/Remove, preventing unintended mutations.
Q: What's the risk of returning ICollection? A: Callers can mutate the collection, potentially breaking invariants. Return defensive copies or IReadOnlyCollection if modification isn't intended.
Q: How does ICollection relate to arrays? A: Arrays implement ICollection, but Add/Remove throw NotSupportedException because arrays are fixed-size. Check IsReadOnly before assuming mutability.
Q: When should I use HashSet vs List for ICollection? A: HashSet when uniqueness matters and Contains() is frequent (O(1)). List when order matters and indexed access is needed. Both implement ICollection.
Q: Can ICollection be lazy like IEnumerable? A: No. Count property implies materialization. Returning ICollection signals that data is already loaded and countable.
Q: How do I mock ICollection in tests? A: Use List or HashSet directly, or create a custom fake. Most mocking frameworks (Moq, NSubstitute) can stub ICollection methods.
Q: What's the performance of Contains() on ICollection? A: Depends on implementation. HashSet is O(1), List is O(n). Always consider the concrete type when performance matters, even if accepting ICollection.

See also