139

Is there any reason to expose an internal collection as a ReadOnlyCollection rather than an IEnumerable if the calling code only iterates over the collection?

class Bar
{
    private ICollection<Foo> foos;
    
    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}


// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);

As I see it IEnumerable is a subset of the interface of ReadOnlyCollection and it does not allow the user to modify the collection. So if the IEnumberable interface is enough then that is the one to use. Is that a proper way of reasoning about it or am I missing something?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Erik Öjebo
  • 10,821
  • 4
  • 54
  • 75
  • 7
    If you're using .NET 4.5, you may want to try the new [read-only collection interfaces](http://msdn.microsoft.com/en-us/magazine/jj133817.aspx). You'll still want to wrap the returned collection in a `ReadOnlyCollection` if you're paranoid, but now you're not tied to a specific implementation. – StriplingWarrior Nov 12 '12 at 22:09

5 Answers5

105

More modern solution

Unless you need the internal collection to be mutable, you could use the System.Collections.Immutable package, change your field type to be an immutable collection, and then expose that directly - assuming Foo itself is immutable, of course.

Updated answer to address the question more directly

Is there any reason to expose an internal collection as a ReadOnlyCollection rather than an IEnumerable if the calling code only iterates over the collection?

It depends on how much you trust the calling code. If you're in complete control over everything that will ever call this member and you guarantee that no code will ever use:

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);

then sure, no harm will be done if you just return the collection directly. I generally try to be a bit more paranoid than that though.

Likewise, as you say: if you only need IEnumerable<T>, then why tie yourself to anything stronger?

Original answer

If you're using .NET 3.5, you can avoid making a copy and avoid the simple cast by using a simple call to Skip:

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}

(There are plenty of other options for wrapping trivially - the nice thing about Skip over Select/Where is that there's no delegate to execute pointlessly for each iteration.)

If you're not using .NET 3.5 you can write a very simple wrapper to do the same thing:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 16
    Note that there is a performance penalty for this: AFAIK the Enumerable.Count method is optimized for Collections casted into IEnumerables, but not for a ranges produced by Skip(0) – shojtsy Feb 01 '10 at 23:45
  • If wrapping a List in a class, would it be better to do the above or have the wrapping class implement IEnumerable and pass on GetEnumerator()? – andleer Jul 25 '10 at 18:04
  • @Andrew: It depends on the exact situation. Do you want your wrapping class act as the collection itself, or merely *contain* a collection? – Jon Skeet Jul 25 '10 at 18:28
  • @Jon, good question. I guess depending on which way one wanted to answer this either approach could be good. In my case, I think it is the collection itself so assuming passing on GetEnumerator() is a valid approach, then that is the way I would go. Thanks, – andleer Jul 26 '10 at 00:01
  • I'm slightly surprised that foos.Skip(0) does not compile to the same MSIL as foos.Select(x => x)... Does the use of an delegate always carry overhead? – Silas Davis May 24 '12 at 18:26
  • @silasdavis: Why on earth would the compiler want to know about `Skip` vs `Select` calls? *Executing* a delegate will always carry an overhead, although it's likely to be minimal. – Jon Skeet May 24 '12 at 18:28
  • 7
    -1 Is it just me or is this an answer to a different question? It's useful to know this "solution", but the op has asked for a comparison between returning a ReadOnlyCollection and IEnumerable. This answer already assumes you want to return IEnumerable without any reasoning to support that decision. – Zaid Masud Aug 08 '12 at 15:35
  • 1
    The answer shows casting a `ReadOnlyCollection` to an `ICollection` and then running `Add` successfully. As far as I know, that should not be possible. The `evil.Add(...)` should throw an error. https://dotnetfiddle.net/GII2jW – Shaun Luttin Oct 26 '15 at 16:42
  • 2
    @ShaunLuttin: Yes, exactly - that throws. But in my answer, I don't cast a `ReadOnlyCollection` - I cast an `IEnumerable` which isn't actually read-only... it's *instead* of exposing a `ReadOnlyCollection` – Jon Skeet Oct 26 '15 at 16:45
  • 1
    Aha. Your paranoia would lead you to use a `ReadOnlyCollection` instead of an `IEnumerable`, in order to protect against the evil casting. – Shaun Luttin Oct 26 '15 at 16:48
  • 1
    @ShaunLuttin: Instead of exposing the underlying `List` or whatever, yes. – Jon Skeet Oct 26 '15 at 16:48
  • @JonSkeet - is it fine to use foos.AsEnumerable() instead of foos.Skip(0) ? – Ananth Aug 07 '17 at 17:24
  • 1
    @Ananth: No, because that's a no-op - the returned value could still be cast back to the collection type. – Jon Skeet Aug 07 '17 at 18:14
  • Given the popularity of this answer, I wonder if it might be worth it to add to it a couple of cents on `System.Collections.Immutable`? – fuglede Aug 17 '17 at 17:23
49

If you only need to iterate through the collection:

foreach (Foo f in bar.Foos)

then returning IEnumerable is enough.

If you need random access to items:

Foo f = bar.Foos[17];

then wrap it in ReadOnlyCollection.

Vojislav Stojkovic
  • 8,043
  • 4
  • 35
  • 48
  • 2
    @w0051977 It depends on your intent. Using `System.Collections.Immutable` as Jon Skeet recommends is perfectly valid when you're exposing something that is never going to change after you get a reference to it. On the other hand, if you're exposing a read-only view of a mutable collection, then this advice is still valid, although I would probably expose it as `IReadOnlyCollection` (which didn't exist when I wrote this). – Vojislav Stojkovic Apr 16 '18 at 14:41
  • `IReadOnlyList` has the `[index]`er – Steve Desmond May 13 '22 at 14:53
35

If you do this then there's nothing stopping your callers casting the IEnumerable back to ICollection and then modifying it. ReadOnlyCollection removes this possibility, although it's still possible to access the underlying writable collection via reflection. If the collection is small then a safe and easy way to get around this problem is to return a copy instead.

Stu Mackellar
  • 11,510
  • 1
  • 38
  • 59
  • 17
    That's the kind of "paranoid design" I heartily disagree with. I think it's bad practice to choose inadequate semantics in order to enforce a policy. – Vojislav Stojkovic Jan 29 '09 at 12:37
  • 26
    You disagreeing doesn't make it wrong. If I'm designing a library for external distribution I'd much rather have a paranoid interface than deal with bugs raised by users who try to misuse the API. See the C# design guidelines at http://msdn.microsoft.com/en-us/library/k2604h5s(VS.71).aspx – Stu Mackellar Jan 29 '09 at 12:44
  • 5
    Yes, in the specific case of designing a library for external distribution, it makes sense to have a paranoid interface for whatever you're exposing. Even so, if the semantics of Foos property is sequential access, use ReadOnlyCollection and then return IEnumerable. No need to use wrong semantics ;) – Vojislav Stojkovic Jan 29 '09 at 12:58
  • 10
    You don't need to do either. You can return a read-only iterator without copying... – Jon Skeet Jan 29 '09 at 13:33
  • You don't need reflection to get back the underlying writable collection from a ReadOnlyCollection. Only (readCollection.GetEnumerator() as ICollection). – shojtsy Feb 02 '10 at 00:12
  • 1
    @shojtsy Your line of code doesn't seem to work. _as_ generates a _null_. A cast throws an exception. – Nick Alexeev May 31 '11 at 05:27
  • 1
    I believe the correct syntax is `readOnlyCollection.Items` which returns an `IList` that can be modified. – MEMark Sep 07 '11 at 18:07
  • Why did you say "ReadOnlyCollection removes this possibility"? ReadOnlyCollection has implemented ICollection explicitly, thus nothing stops you from assigning a ReadOnlyCollection instance to a ICollect variable as well? – joe Feb 25 '21 at 03:01
  • @joe Given that `ReadOnlyCollection`'s implementation of `ICollection` will cause exceptions to be thrown whenever the latter's edit methods are called, the statement is indeed accurate. – Crono Jun 05 '23 at 13:23
3

I avoid using ReadOnlyCollection as much as possible, it is actually considerably slower than just using a normal List. See this example:

List<int> intList = new List<int>();
        //Use a ReadOnlyCollection around the List
        System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);

        for (int i = 0; i < 100000000; i++)
        {
            intList.Add(i);
        }
        long result = 0;

        //Use normal foreach on the ReadOnlyCollection
        TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
        foreach (int i in mValue)
            result += i;
        TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

        //use <list>.ForEach
        lStart = new TimeSpan(System.DateTime.Now.Ticks);
        result = 0;
        intList.ForEach(delegate(int i) { result += i; });
        lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());
James Madison
  • 55
  • 1
  • 1
  • 16
    Premature optimization. By my measurements, iterating over a ReadOnlyCollection takes roughly 36% longer than a regular list, assuming you do *nothing* inside the for loop. Chances are, you'll never notice this difference, but if this is a hot-spot in your code and requires every last bit of performance you can squeeze out of it, why not use an Array instead? That would be faster still. – StriplingWarrior Nov 12 '12 at 22:06
  • Your benchmark contains flaws, you are completely ignoring branch prediction. – Enes Sadık Özbek Feb 15 '19 at 15:42
0

Sometimes you may want to use an interface, perhaps because you want to mock the collection during unit testing. Please see my blog entry for adding your own interface to ReadonlyCollection by using an adapter.