ICollection
3 min readICollection
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
âś… 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
🔥 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.