112

I have spent quite a few hours pondering the subject of exposing list members. In a similar question to mine, Jon Skeet gave an excellent answer. Please feel free to have a look.

ReadOnlyCollection or IEnumerable for exposing member collections?

I am usually quite paranoid to exposing lists, especially if you are developing an API.

I have always used IEnumerable for exposing lists, as it is quite safe, and it gives that much flexibility. Let me use an example here:

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IEnumerable<WorkItem> WorkItems
    {
        get
        {
            return this.workItems;
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

Anyone who codes against an IEnumerable is quite safe here. If I later decide to use an ordered list or something, none of their code breaks and it is still nice. The downside of this is IEnumerable can be cast back to a list outside of this class.

For this reason, a lot of developers use ReadOnlyCollection for exposing a member. This is quite safe since it can never be cast back to a list. For me I prefer IEnumerable since it provides more flexibility, should I ever want to implement something different than a list.

I have come up with a new idea I like better. Using IReadOnlyCollection:

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IReadOnlyCollection<WorkItem> WorkItems
    {
        get
        {
            return new ReadOnlyCollection<WorkItem>(this.workItems);
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

I feel this retains some of the flexibility of IEnumerable and is encapsulated quite nicely.

I posted this question to get some input on my idea. Do you prefer this solution to IEnumerable? Do you think it is better to use a concrete return value of ReadOnlyCollection? This is quite a debate and I want to try and see what are the advantages/disadvantages that we all can come up with.

EDIT

First of all thank you all for contributing so much to the discussion here. I have certainly learned a ton from each and every one and would like to thank you sincerely.

I am adding some extra scenarios and info.

There are some common pitfalls with IReadOnlyCollection and IEnumerable.

Consider the example below:

public IReadOnlyCollection<WorkItem> WorkItems
{
    get
    {
        return this.workItems;
    }
}

The above example can be casted back to a list and mutated, even though the interface is readonly. The interface, despite it's namesake does not guarantee immutability. It is up to you to provide an immutable solution, therefore you should return a new ReadOnlyCollection. By creating a new list (a copy essentially), the state of your object is safe and sound.

Richiban says it best in his comment: a interface only guarantees what something can do, not what it cannot do.

See below for an example:

public IEnumerable<WorkItem> WorkItems
{
    get
    {
        return new List<WorkItem>(this.workItems);
    }
}

The above can be casted and mutated, but your object is still immutable.

Another outside the box statement would be collection classes. Consider the following:

public class Bar : IEnumerable<string>
{
    private List<string> foo;

    public Bar()
    {
        this.foo = new List<string> { "123", "456" };
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this.foo.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

The class above can have methods for mutating foo the way you want it to be, but your object can never be casted to a list of any sort and mutated.

Carsten Führmann makes a fantastic point about yield return statements in IEnumerables.

starball
  • 20,030
  • 7
  • 43
  • 238
Marcel De Villiers
  • 1,682
  • 4
  • 15
  • 17
  • 2
    Hmm I think you should read great Jon Skeet anwser: http://stackoverflow.com/questions/491375/readonlycollection-or-ienumerable-for-exposing-member-collections/491591#491591 [Possible duplicate] –  Jul 22 '14 at 06:59
  • 2
    If you read the question from the top you will find that I referenced John Skeet's answer in my question as a similar question. :) Although the answer is very well written, I believe there are some topics not completely touched on this – Marcel De Villiers Jul 22 '14 at 07:03
  • No problem. Easy to miss it. :) – Marcel De Villiers Jul 22 '14 at 07:08
  • I fail to understand how IReadOnlyCollection can never to cast to List, Cant we do this WorkItems.AsEnumerable().ToList(); ?? – Ananth Aug 07 '17 at 17:52
  • 1
    Hi Ananth. Good question. It's been quite a while since I have thought of this question and there is much I have learned since. Using WorkItmes.AsEnumerable().ToList() will create a copy of the list and your class won't be mutated. In the case as above, because a new readonly collection is returned, you will never be able to cast it back to a list. Your cast will be null With this said, there is a common pitfall with IReadOnlyCollection. If you return the list in your get, someone can cast it to a list and mutate the state of your object. I will update the question for clarity – Marcel De Villiers Aug 08 '17 at 19:04
  • Hi Marcel-Is-Hier - Thanks so much for explaining. Now I understand. – Ananth Aug 09 '17 at 00:08
  • How? Why? What? You are creating new instances of ReadOnlyCollection everytime WorkItems property is called. In what world does this make sense to any one C# programmer? – Xtro Mar 11 '23 at 03:22

5 Answers5

133

One important aspect seems to be missing from the answers so far:

When an IEnumerable<T> is returned to the caller, they must consider the possibility that the returned object is a "lazy stream", e.g. a collection built with "yield return". That is, the performance penalty for producing the elements of the IEnumerable<T> may have to be paid by the caller, for each use of the IEnumerable. (The productivity tool "Resharper" actually points this out as a code smell.)

By contrast, an IReadOnlyCollection<T> signals to the caller that there will be no lazy evaluation. (The Count property, as opposed to the Count extension method of IEnumerable<T> (which is inherited by IReadOnlyCollection<T> so it has the method as well), signals non-lazyness. And so does the fact that there seem to be no lazy implementations of IReadOnlyCollection.)

This is also valid for input parameters, as requesting an IReadOnlyCollection<T> instead of IEnumerable<T> signals that the method needs to iterate several times over the collection. Sure the method could create its own list from the IEnumerable<T> and iterate over that, but as the caller may already have a loaded collection at hand it would make sense to take advantage of it whenever possible. If the caller only has an IEnumerable<T> at hand, he only needs to add .ToArray() or .ToList() to the parameter.

What IReadOnlyCollection does not do is prevent the caller to cast to some other collection type. For such protection, one would have to use the class ReadOnlyCollection<T>.

In summary, the only thing IReadOnlyCollection<T> does relative to IEnumerable<T> is add a Count property and thus signal that no lazyness is involved.

Alexander Derck
  • 13,818
  • 5
  • 54
  • 76
Carsten Führmann
  • 3,119
  • 4
  • 26
  • 24
  • 7
    Unfortunately, nothing in IReadOnly* indicates whether an implementation claims to be immutable, or even read-only for that matter (a read-only object may be safely shared with code which must not be allowed to change an object, but would be allowed to see future changes to an object made by someone else who has a "special" reference related to it; many classes which implement IReadOnly* do not meet that criterion). – supercat Oct 29 '15 at 21:37
  • @supercat Correct, it's the unfortunate situation we're in due to the fact that the base interfaces (IList, ISet etc) are from a time when immutable collections weren't the done thing... So "IReadOnly*" actually just means "no mutation methods are exposed here". You can see this from the fact that List(T) implements IReadOnlyList(T) even though it is clearly not readonly! But what else would you call that interface? – Richiban Mar 29 '16 at 14:11
  • 1
    @Richiban: I would call it `IReadableList`. I would reserve `IReadOnlyXX` for things which guarantee that no legitimate implementations will *expose* means of mutation (implying that they may be safely shared with things that must not be allowed to modify the underlying collection) but would not promise that the underlying data store won't be modified in other ways. – supercat Mar 29 '16 at 15:01
  • 4
    @supercat Yeah, `IReadableList` isn't bad... but `IReadOnly*` doesn't make any sense at all because an interface only guarantees what something *can* do, not what it *cannot* do. – Richiban Mar 29 '16 at 16:41
  • @Richiban: The purpose of an interface is to define what recipients of an object *can do with it*. If an object legitimately implements `IReadOnly*` as I would have defined it, that would imply that recipients *can* safely expose it to outside it without having to wrap it first. Likewise, if an object implements `IImmutable*`, recipients *can* safely use a reference to the object as a means of encapsulating its contents, without having to make a defensive copy. In many cases, it may be helpful to have a sealed class which implements one of those interfaces inherit from a class... – supercat Mar 29 '16 at 16:56
  • ...which is functionally identical but doesn't. While some people might balk at the idea of having an "immutable" interface, I would see great advantages in being able to have immutable collection types generate content on demand. – supercat Mar 29 '16 at 16:59
  • Personally I think ReadOnly interfaces are fundamentally stupid because in 99% of cases the _most proper and safe approach_ is thin value-type wrappers that contain only a single private readonly field of the wrapped type and exposes only the non-mutating methods and properties of the that type. **a)** Coding this is as easy as coding a similar class + overriding Equals to make a reference comparison on the private field, **b)** There are no "casting" issues. **c)** Creating instances is more performant, and **d)** implicit cast from the wrapped type to the read-only struct is perfectly safe! – AnorZaken Apr 04 '16 at 03:12
  • ...having a mutable type implement a read-only interface is just plain bad and gets us back in that old "but the caller can cast it" pit. After all _that_ is why using the ReadOnly-classes is encouraged. So then some might think "but what if I want to make my own implementation?" Yeah - no problem! Implement a **private / internal** class that inherits the same mutable interface that is stored in the desired readonly-struct - except you can leave all mutating methods unimplemented since they will never be called 100% guaranteed (unless _you_ do something very wrong - i.e. an _internal_ bug). – AnorZaken Apr 04 '16 at 03:21
  • What I meant by safe implicit casts is that generally speaking implicit casts is a source of bugs, but in this case you'd be hard pressed to make anything go wrong. Simple example: `class MyClass { private int[] myNumbers; ReadOnlyArray MyNumbers { get { return myNumbers; } } }` where `ReadOnlyArray` is a thin generic wrapper struct around an array (with an implicit cast from `T[]`). Such structs should be in included in .net by default - it would make all kinds of read-only things better imo. – AnorZaken Apr 04 '16 at 03:28
  • I too prefer to use `IReadOnlyCollection` wherever possible to guarantee the *least*. `IEnumerable` could be a lazy sequence, and `ICollection`, though in-memory, has Add/Remove methods.. `IReadOnlyCollection` has none of the problems - no order, no mutation methods or other guarantees but is loaded in memory (has Count property), brilliant! :) – nawfal Sep 19 '18 at 09:31
  • 3
    @nawfal Unfortunately, ICollection and IReadOnlyCollection both inherit from IEnumerable, but not each other. They both provide a property _Count_, but here is the catch: LINQ's _Count()_ extension method is optimized for ICollection, not IReadOnlyCollection. This is most unfortunate. https://github.com/microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs –  Jul 20 '19 at 18:28
  • 2
    @dfhwze Your remark is troubling news. I checked several of Microsoft's derivatives of `IReadOnlyCollection`. Fortunately, all of them also implement `ICollection` or `ICollection`. Still, Microsoft left a trap for people writing their own derivatives of `IReadOnlyCollection`. – Carsten Führmann Jul 20 '19 at 21:27
  • @CarstenFührmann The only reason I can think of why they designed it this way, is that whoever implements IReadOnly.. should allow eager enumeration, rendering _Count()_ almost as fast as _Count_. But I agree, they should have included both interfaces for optimization. Or why not Make _ICollection_ inherit _IReadOnlyCollection_ instead, C# team? –  Jul 20 '19 at 21:39
  • 1
    https://github.com/dotnet/runtime/issues/16151 - one of important the reasons why ICollection does not inherit from IReadOnlyCollection: it would break things. – user2864740 Dec 30 '20 at 07:59
  • 1
    Note that having a `Count` property does _not_ signal that elements are loaded eagerly. It is entirely possible to have an object that knows ahead of time how many elements will be returned when they are enumerated but that still loads or computes them lazily. – Steve Hart Mar 26 '21 at 15:36
  • @SteveHart Thanks for pointing that out. You're technically correct of course. I'd say "`Count` signals eagerness" is analogous to "wings signal the ability to fly", which is false e.g. for ostriches. – Carsten Führmann Mar 27 '21 at 22:11
31

Talking about class libraries, I think IReadOnly* is really useful, and I think you're doing it right :)

It's all about immutable collection... Before there were just immutables and to enlarge arrays was a huge task, so .net decided to include in the framework something different, mutable collection, that implement the ugly stuff for you, but IMHO they didn't give you a proper direction for immutable that are extremely useful, especially in a high concurrency scenario where sharing mutable stuff is always a PITA.

If you check other today languages, such as objective-c, you will see that in fact the rules are completely inverted! They quite always exchange immutable collection between different classes, in other words the interface expose just immutable, and internally they use mutable collection (yes, they have it of course), instead they expose proper methods if they want let the outsiders change the collection (if the class is a stateful class).

So this little experience that I've got with other languages pushes me to think that .net list are so powerful, but the immutable collection were there for some reason :)

In this case is not a matter of helping the caller of an interface, to avoid him to change all the code if you're changing internal implementation, like it is with IList vs List, but with IReadOnly* you're protecting yourself, your class, to being used in not a proper way, to avoid useless protection code, code that sometimes you couldn't also write (in the past in some piece of code I had to return a clone of the complete list to avoid this problem).

Michael Denny
  • 906
  • 5
  • 12
  • Very nice answer. The whole idea comes down to one word. Encapsulation. I am always paranoid when I see people exposing a list as a property with a get and set. So dangerous and guaranteed to be misused. Very interesting how other languages handle immutables – Marcel De Villiers Jul 22 '14 at 07:44
  • Thanks! Just for record the mutable/immutable that I was speaking about in objective-c are respectively [NSArray](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html#//apple_ref/occ/cl/NSArray) and [NSMutableArray](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html), the name also speak itself :) – Michael Denny Jul 22 '14 at 08:18
14

My take on concerns of casting and IReadOnly* contracts, and 'proper' usages of such.

If some code is being “clever” enough to perform an explicit cast and break the interface contract, then it is also “clever” enough to use reflection or otherwise do nefarious things such as access the underlying List of a ReadOnlyCollection wrapper object. I don’t program against such “clever” programmers.

The only thing that I guarantee is that after said IReadOnly*-interface objects are exposed, then my code will not violate that contract and will not modified the returned collection object.

This means that I write code that returns List-as-IReadOnly*, eg., and rarely opt for an actual read-only concrete type or wrapper. Using IEnumerable.ToList is sufficient to return an IReadOnly[List|Collection] - calling List.AsReadOnly adds little value against “clever” programmers who can still access the underlying list that the ReadOnlyCollection wraps.

In all cases, I guarantee that the concrete types of IReadOnly* return values are eager. If I ever write a method that returns an IEnumerable, it is specifically because the contract of the method is that which “supports streaming” fsvo.

As far as IReadOnlyList and IReadOnlyCollection, I use the former when there is 'an' implied stable ordering established that is meaningful to index, regardless of purposeful sorting. For example, arrays and Lists can be returned as an IReadOnlyList while a HashSet would better be returned as an IReadOnlyCollection. The caller can always assign the I[ReadOnly]List to an I[ReadOnly]Collection as desired: this choice is about the contract exposed and not what a programmer, “clever” or otherwise, will do.

user2864740
  • 60,010
  • 15
  • 145
  • 220
6

It seems that you can just return an appropriate interface:

...
    private readonly List<WorkItem> workItems = new List<WorkItem>();

    // Usually, there's no need the property to be virtual 
    public virtual IReadOnlyList<WorkItem> WorkItems {
      get {
        return workItems;
      }
    }
...

Since workItems field is in fact List<T> so the natural idea IMHO is to expose the most wide interface which is IReadOnlyList<T> in the case

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • I never knew of IReadonlyList. I copied this straight from my domain and changed it a bit. NHibernate needs these to be virtual though. I'll change that quickly thanks. I now need to dig a bit on that interface. Nice!!!! – Marcel De Villiers Jul 22 '14 at 07:02
  • 5
    Can't you cast this back into a `List<>` like mentioned in the question for `IEnumerable<>`s? – Rawling Jul 22 '14 at 07:22
  • 1
    @Rawling: Since ReadOnlyCollection inherits from List, casting it to a list will leave it as a ReadOnlyCollection. Doing this and then attempting to add an element will cause a NotSupportedException: Collection is read-only. – Justin J Stark Oct 23 '14 at 15:17
  • 1
    Another alternative is to return an IEnumerable using List.Skip(0) rather than casting to prevent the IEnumerable from being explicitly cast back to a List. See http://stackoverflow.com/a/491591/1025728 – Justin J Stark Oct 23 '14 at 15:32
  • 2
    @JustinJStark `ReadOnlyCollection` is not involved here. There is a `List` hidden behind an `IReadOnlyList` interface and, as the question and your second comment note, you can just cast that back to a `List`. – Rawling Oct 23 '14 at 16:02
  • 3
    Since the returned value can still be cast to `List` and modified, I'd add call to `AsReadOnly` and return that. This will ensure the internal value isn't modified. `get { return workItems.AsReadOnly(); }` – JG in SD May 17 '16 at 16:59
-7

!! IEnumerable vs IReadOnlyList !!

IEnumerable has been with us from the beginning of time. For many years, it was a de facto standard way to represent a read-only collection. Since .NET 4.5, however, there is another way to do that: IReadOnlyList.

Both collection interfaces are useful.

<>