Search code examples
c#icollection

How to turn ICollection<T> into IReadOnlyCollection<T>?


When I have a variable of ICollection<T> in C#, I cannot pass it to a function that expects an IReadOnlyCollection<T>:

public void Foo()
{
  ICollection<int> data = new List<int>();
  // Bar(data); // Not allowed: Cannot implicitly cast ICollection<int> to IReadOnlyCollection<int>
  Bar(data.ToList()); // Works, since List<T> implements IReadOnlyCollection<T>
}

public void Bar(IReadOnlyCollection<int> data)
{
  if (data.Count == 1) { /* ... */ }
  // ...
}

Apparently the problem is that ICollection<T> does not inherit from IReadOnlyCollection<T> - but why? ICollection<T> should be the full functional set of IReadOnlyCollection<T> plus the functions that modify the collection.

And what is the best solution to pass the arguments?

On the one hand, since I don't want to alter the collection in Bar and just need the count and iterate over the collection, I'd like to require an IReadOnlyCollection.

On the other hand, I don't want to create a new list object every time I call that function.


Solution

  • There is no standard solution AFAIK, but it's not hard to make your own like this

    public static class MyExtensions
    {
        public static IReadOnlyCollection<T> AsReadOnly<T>(this ICollection<T> source)
        {
            if (source == null) throw new ArgumentNullException("source");
            return source as IReadOnlyCollection<T> ?? new ReadOnlyCollectionAdapter<T>(source);
        }
    
        sealed class ReadOnlyCollectionAdapter<T> : IReadOnlyCollection<T>
        {
            readonly ICollection<T> source;
            public ReadOnlyCollectionAdapter(ICollection<T> source) => this.source = source;
            public int Count => source.Count;
            public IEnumerator<T> GetEnumerator() => source.GetEnumerator();
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
    }
    

    And then use it as follows

    Bar(data.AsReadOnly());