views:

339

answers:

6

In a response to this question runefs suggested that "unless you have a very specific reason for using IList you should considere IEnumerable". Which do you use and why?

+3  A: 

When I only need to enumerate the children, I use IEnumerable. If I happen to need Count, I use ICollection. I try to avoid that, though, because it exposes implementation details.

Rytmis
+7  A: 

Always use the most restrictive interface that provides the features you need, because that gives you the most flexibility to change the implementation later. So, if IEnumerable<T> is enough, then use that... if you need list-features, use IList<T>.

And preferably use the strongly typed generic versions.

jerryjvl
+10  A: 

IEnumberable<T> is read-only, you have to reconstruct the collection to make changes to it. On the other hand, IList<T> is read-write. So if you expect a lot of changes to the collection, expose IList<T> but if it's safe to assume that you won't modify it, go with IEnumerable<T>.

DrJokepu
I agree. I would add, however, that if you simply return or cast your List<T> instance to IEnumerable<T>, the recipient can always cast it back to IList<T> or List<T>. The correct way to return a trully immutable IEnumerable<T> is to use the AsReadOnly() method which returns a wrapper object that implements IEnumerable<T> and cannot be cast back to a list.
LBushkin
I seem to be in the minority here, but I still don't understand *why* you are advocating IEnumerable. As LBushkin says, it is the wrong way to expose a readonly collection, so if internally you have an object that implements IList and you are willing to expose it why limit the client code to only using the IEnumerable subset of methods? How do you know what the client code is going to want to do with it? Maybe I'm thinking of this from the point of view of writing a framework where the client is abstract, and you are consuming the object in your own code?
Martin Harris
@Martin Harris: IEnumerable<T> is the de facto standard collection container interface in .NET 3.5. I'm not sure if that was a good decision or not but that's the current common practice.
DrJokepu
If you want immutability, I would favour IEnumerable<T> using yield - that way there's no danger of casting back to IList<T>. It can also cut down collection copying. As for why a "thinner" interface should be exposed, the short answer is it reduces coupling between the client and your internal implementation (unless you are transforming an internal collection into an external representation anyway). I also try to push collection-oriented operations into the holder of the collection rather than force clients to do it themselves.
Jim Arnold
Okay, at the risk of being argumentative (and given the current voting situation I guess that would be a bad move) can you give me some solid examples from the .NET 3.5 framework where the internal representation of a property fully implements IList and yet it is exposed as an IEnumerable. A quick scan through reflector doesn't make any jump out, IEnumerable seems to be used mainly where is is yielding in the method so IList wouldn't be an option. If I see where this has been done in the real world maybe I'll grok the reasoning behind it.
Martin Harris
Well, pretty much all LINQ queries return IEnumerable<T> collections. Not just LINQ to SQL, but any LINQ query. That makes it the universal lowest common denominator.
DrJokepu
But isn't that because they aren't actually Lists internally? I'm looking for a situation where Microsoft *could* have returned a IList<whatever> or a List<whatever> and decided to return an IEnumerable<whatever> to reduce coupling. If it is the defacto standard to do this I would expect the child controls in Winforms to be returned as an IEnumerable etc...
Martin Harris
Windows Forms and the majority of the .NET Frameworks predates LINQ and even generics. The IEnumerable<T> doctrine was introduced in 3.5 with LINQ.
DrJokepu
So then point me to an object that has a MemoryStream as a field and exposes it as a IStream property, or an Array as a field and exposes it as IEnumerable. In fact I'd be interested to see any code in the framework where a property is exposing a field as a more base type than it really is.
Martin Harris
Here's an example: the underlying stream of HttpWebRequest and HttpWebResponse is exposed as a Stream (even internally) even though in practice it is always an instance of the internal ConnectionStream class. For public methods/properties exposing IEnumerable<T> interfaces you need to look for classes introduced in 3.5, one example is the System.Diagnostics.Eventing.Reader.EventLogConfiguration.ProviderNames property.
DrJokepu
Who cares whether Microsoft do it or not? They hardly have a track record of providing great software design guidance :-) The lack of interfaces and abstraction in the base .Net libraries is a constant source of annoyance, not a recipe for you to follow blindly.
Jim Arnold
@DrJokepu - Thanks for the references, I'll have a look and see if I can learn anything about why they did it this way.@Jim Arnold - How is it a source of annoyance in return types? When have you ever said "God, I wish Microsoft had returned an IEnumerable here rather than the far more feature rich concrete class", I can see how it would be an annoyance if they didn'e *accept* them, but how can a return value be not abstract enough?
Martin Harris
Hey, look at that! You are completely right about the ProviderNames property. Internally it is an array of strings and they expose it as an IEnumerable. If I had to guess why I would say that they didn't want old arrays floating around in their shiny new 3.5 code, but honestly I didn't think you'd find any, and it is a perfect example. Thanks for enlightning me.
Martin Harris
It is also important to keep in mind that Microsoft may have other concerns in the design of its libraries that are not going to apply to an in-house library. If Microsoft exposes a List as an IEnumerable, then the turn-around time of giving access to list methods if someone claims to need them is about 2 years for the next .NET release. By virtue of their position they may very well not be able to afford restricting the return types of their APIs as much as anyone else could.
jerryjvl
Which is precisely my point, since the original question wasn't "How should I expose an IList when I know exactly what functionality the client code is going to use from it, and they won't ever need to use more functionality than IEnumerable supplies" I'm advocating the policy of giving the client the most functionality that you can since you don't know what they'll need. My approach appears to be the standard for API development (looking at Microsoft, Telerik and Infragistics code) so I would argue that the quote in the question is backwards and without knowing specifics you should use IList.
Martin Harris
@Martin - in my mind the issue isn't one of limiting a client's options, it's purely one of dependency management. Less abstract interfaces are more likely to change than more abstract ones. In addition, framework API design is a very different beast to internal application design, and has different concerns. Anyway, sorry @DrJokepu for hijacking your answer :-)
Jim Arnold
Martin Harris, Jim Arnold: I've really enjoyed this discussion, thank you for sharing your thoughts.
DrJokepu
Ah well, the answer has been accepted now, and despite my most valiant efforts I'm still sit at a grand total of zero upvotes so I guess the rest of the world just doesn't see it my way (apart from perhaps Jeffrey Richter - I wish I could source that quote). I've enjoyed the conversation and apologise for turning this QA site into a discussion forum. I'll bow out now and quietly leave (muttering under my breathe about how one day my misunderstood genius will be appreciated :-)).
Martin Harris
+2  A: 

IList<T> is indeed a very beefy interface. I prefer to expose Collection<T>-derived types. This is pretty much in line with what Jeffrey Richter suggests doing (don't have a book nearby, so can't specify page/chapter number): methods should accept the most common types as parameters and return the most "derived" types as return values.

Anton Gogolev
Hey, I'm not on my own! That quote from Jeffery Richter looks like it's the one that I'm talking about in my answer. If you get a chance would you mind digging out a reference as I'm having no luck at all search for it online?
Martin Harris
+3  A: 

The principle I follow is one I read a while back:

"Consume the simplest and expose the most complex"

(I'm sure this is really common and I'm mis-quoting it, if anyone can knows the source can you leave a comment or edit please...)

(Edited to add - well, here I am a couple of weeks later and I've just ran into this quote in a completely different context, it looks like it started as the Robustness Principle or Postel's Law -

Be conservative in what you do; be liberal in what you accept from others.

The original definition is for network communication over the internet, but I'm sure I've seen it repurposed for defining class contracts in OO.)

Basically, if you're defining a method for external consumption then the parameters should be the most basic type that gives you the functionality you require - in the case of your example this may mean taking in an IEnumerable instead of an IList. This gives client code the most flexibility over what they can pass in. On the other hand if you are exposing a property for external consumption then do so with the most complex type (IList or ICollection instead of IEnumerable) since this gives the client the most flexibility in the way they use the object.


I'm finding the conversation between myself and DrJokepu in the comments fascinating, but I also appreciate that this isn't supposed to be a discussion forum so I'll edit my answer to further outline the reasons behind my choice to buck the trend and suggest that you expose it as an IList (well a List actually, as you'll see). Let's say this is the class we are talking about:

public class Example
{
    private List<int> _internal = new List<int>();

    public /*To be decided*/ External
    {
        get { return _internal; }
    }
}

So first of all let's assume that we are exposing External as an IEnumerable. The reasons I have seen for doing this in the other answers and comments are currently:

  • IEnumerable is read-only
  • IEnumerable is the defacto standard
  • It reduces coupling so you can change the implementation
  • You don't expose implementation details

While IEnumerable only exposes readonly functionality that doesn't make it readonly. The real type you are returning is easily found out by reflection or simply pausing a debugger and looking, so it is trivial to cast it back to List<>, the same applies to the FileStream example in the comments below. If you are trying to protect the member then pretending it is something else isn't the way to do it.

I don't believe it is the defacto standard. I can't find any code in the .NET 3.5 library where Microsoft had the option to return an concrete collection and returned an interface instead. IEnumerable is more common in 3.5 thanks to LINQ, but that is because they can't expose a more derived type, not because they don't want to.

Now, reducing coupling I agree with - to a point - basically this argument seems to be that you are telling the client that while the object you return is a list I want you to treat is as an IEnumerable just in case you decide to change the internal code later. Ignoring the problems with this and the fact that the real type is exposed anyway it still leaves the question of "why stop at IEnumerable?" if you return it as an object type then you'll have complete freedom to change the implementation to anything! This means that you must have made a decision about how much functionality the client code requires so either you are writing the client code, or the decision is based around arbitrary metrics.

Finally, as previously discussed, you can assume that implementation details are always exposed in .NET, especially if you are publicly exposing the object.

So, after that long diatribe there is one question left - Why expose it as a List<>? Well, why not? You haven't gained anything by exposing it as a IEnumerable, so why artificially limit the client code's ability to work with the object? Imagine if Microsoft had decided that the Controls collection of a Winforms Control should appear as IEnumerable instead of ControlCollection - you'd no longer be able to pass it to methods that require an IList, work on items at an arbitrary index, or see if it contains a particular control unless you cast it. Microsoft wouldn't really have gained anything, and it would just inconvenience you.

Martin Harris
And what happens if for some reason, you have to change the way the backend works so that it will have to use a different datastructure? Let's suppose that previously the backend always knew the number of items it returned before constructing a collection, so according to this principle, it returned an array. And then some day the circumstances change and you have to use a vector or a linked list going forward. Are you going to jump through hoops to convert back it to an array or would you prefer refactoring the client code, possibly breaking third party code that depends on your interface?
DrJokepu
Well, you've already made the decision to define a type for your public properties otherwise you would advocate exposing all types as "object" which would give you the most flexibility to change the backend implementation later. Once you have decided what a client should be able to do with your code (for example you may not support adding new items, which rules out using an IList) why artificially limit them just in case of the possibility that you may change your mind later?
Martin Harris
Here's what I think: let's suppose we have two interfaces (or classes or whatever), I1 and I2, and I1 < I2, that is, I2 fully implements I1. If exposing I1 is enough to do the job, I2 means exposing features that aren't necessarily needed but have to be maintained or supported. That may not be a problem with simple, premade structures such as collections, but I can easily imagine situations where this could lead to feature creep/the client abusing the API in more complex cases.
DrJokepu
This is what I don't understand - and I appreciate you going backwards and forwards with me on this - how do you know that "exposing I1 is enough to do the job"? What job? How do you know what functionality the client code wants from your object?
Martin Harris
Ok, consider that. You create a class that allows the client code to write to a stream. Now internally, you know that this stream is always a FileStream, and never a NetworkStream. You could expose a FileStream as opposed to exposing just a stream, but that would mean that the client would have access to FileStream.SafeHandle, that is, the Windows API handle of the stream. Now you can either prepare your code to expect that the client will mess with the file directly through Windows API or just cross your fingers and hope that this will never happen.
DrJokepu
I'm edited my answer to fully explain my point of view. Hopefully this clears up why I don't agree with you, but I am enjoying the conversation, so please feel free to continue it.
Martin Harris
An upvote! One glorious upvote! Whoever that was let me know if you are ever in London and I'll buy you a beer. I feel (slightly) vindicated now.
Martin Harris
Meh. Have another one :)
Jim Arnold
A: 

If a read-only collection is exposed via a property, then the conventional way to do it is to expose it as ReadOnlyCollection (or a derived class of that) wrapping whatever you have there. It exposes full capabilities of IList to the client, and yet it makes it very clear that it is read-only.

Pavel Minaev