tags:

views:

235

answers:

6

Which is the best type to us for returning collections?

Should I use IList<T>, IEnumerable<T>, IQueryable<T>, something else? Which is best and why?

I'm trying to decide which I should use typically, both in the interface and the implementation of a few classes that I'm writing.

edit Let me nail this down a little further, I am using LINQ to SQL to return data over a WCF Service. It feels like that may make a change in the best type to use?

+4  A: 

Use the least general Type that all posssible return types will conform to. i.e, if the method you are looking at might return a List<int> or an int[], then I'd type as IEnumerable<int> ... If it could return List<int> or a List<Employee> or an int[] I'd type as IEnumerable. If it always returned either a Collection<Employee> or a Collection<SalariedEmployee> then return Collection<Employee>

If the method will always generate the same type, use that type...

In a consuming method or interface, otoh, where the returned object is being used, you should use the opposite philosophy, Type the incoming method parameter as the least general type that is required by the internal funtionality of the code in the comsuming method... i.e, if all the method does with the collection object is enumerate through it using foreach, then the incoming parameter type should IEnumerable<>

Charles Bretana
+1  A: 

I default to IEnumerable. I'm shooting for the minimal interface to expose. Both IList<T> and IQueryable<T> implement IEnumerable<T>. So unless you have other specific requirements for the methods I'd go for minimalism and use the least derived type. If you have other requirements in your calling code, such as performance of indexed lookups or getting the number of items in the collection then you might want to choose another type such as ICollection<T>.

Mike Two
IEnumerable<T> makes sense if you are sure that the only scenario you will ever want to support is forward-only iteration.
fatcat1111
@fatcat1111 - You are right, but that is generally the only scenario I want to support if I'm returning a collection. If I want to modify the collection or do something else then I'll provide descriptive methods for that. I won't return an IList<Employee> so someone can call Add on it. I'll have an AddEmployee method.
Mike Two
That is exactly what I'm thinking. That supports using WCF or other service based architecture.
Nate Bross
@Mike Two: I agree that just returning the List<T> as an IList<T> exposes it to unwanted changes, but what about returning List<T>.AsReadOnly() instead? This lets you get the Count, access members by index and so on.
Steven Sudit
@Steven - But you can do all of those things with IEnumerable<T> as well. Returning List<T>.AsReadOnly gives the caller a strong hint that the underlying storage is List<T>. I want to keep my options open. So I always start the external interface as IEnumerable<T> until it needs to be something else.
Mike Two
So, when returning IEnumerable<T>, how does your calling code look? Do you do `List<T> = new List<T>(obj.ReturnIEnumerable());`
Nate Bross
@Nate - more like `IEnumerable<T> result = obj.ReturnIEnumerable();` You can have variables that are declared as an interface type. It actually allows you more flexibility in how you write `ReturnIEnumerable()` It could return a List<T> or a HashSet<T> or a LinkedList<T> or it could be doing a `yield return ...` Your caller never needs to know. If the contract of IEnumerable<T> is enough, and thanks to LINQ it often is, then leave it as IEnumerable<T>.
Mike Two
@Mike Two: You can't actually get the count of an IEnumerable<T> without enumerating through it. Likewise, you can't access members by index. Sure, you could use ToList() to create a new list from the enumerable, but that's neither obvious nor free.
Steven Sudit
@Steven Sudit - You can get the count in .Net 3.5. The extension methods in Enumerable add a Count method, not property, and the ElementAt(int) method.
Mike Two
@Mike Two: Right, but these methods would have to run the iterator through, so it's not free. Nothing in IEnumerable allows determination of count, short of calling MoveNext until it fails.
Steven Sudit
@Steven - No the implementation of `Enumerable.Count()` is smarter than that. It actually can tell if the underlying implementation is `ICollection` and it will do the right when it is. Either way is that a detail the consumer of the class should care about?
Mike Two
@Mike Two: Yes, it is. The caller is responsible for any performance problems caused by the assumptions it makes. If it counts on the IEnumerable being castable to ICollection, it's violating encapsulation. In this case, it should return an ICollection.
Steven Sudit
@Steven - Yes the caller is responsible for the assumptions it makes. The point was that you said it had to run the iterator through and I pointed out that the built in Count() method did not have to do that. It does not count on it being ICollection and does not break encapsulation. The point of returning IEnumerable is to preserve encapsulation. Use the least derived type. If the caller has strict performance requirements then that must be designed in and then IEnumerable may not be appropriate. The question is about the general case so in general I prefer IEnumerable.
Mike Two
@Mike Two: Imagine that my `Foo.GetList()` returns an `IEnumerable<T>` that is coincidentally implemented as a `List<T>`, and your `Moo.ProcessList(Foo f)` depends on `Count()` using this short-cut. Then I change my mind about implenentation and instead loop through a larger `List<T>`, calling `yield return` selectively. Suddenly, your code slows down or stops working. This is what I mean by violating encapsulation: in practice, your code is depending on an implementation detail that may change. If I really wanted to guarantee a fast, repeatable `Count`, I'd return `ICollection<T>`.
Steven Sudit
@Steven - Yes I already understand your point. I did not say you should depend on the existence of the shortcut. I only pointed out that you said that to get the count you would have to iterate through. I pointed out that there were cases where you wouldn't. I did not say you should depend on it. We are actually in agreement on most of it. I did not say you should depend on the behavior of count, only pointed out what it was. I agree that if you wanted to guarantee fast repeatable count, which was not mentioned in the question, you should return ICollection. It is a requirement that you added.
Mike Two
@Mike Two: It looks like we should just agree to agree, and be done with it.
Steven Sudit
+4  A: 

The Framework Design Guidelines state:

Use Collection<T> or a subclass of Collection<T> for properties or return values representing read/write collections.

public Collection<Session> Sessions { get; }

Use ReadOnlyCollection<T>, a subclass of ReadOnlyCollection<T>, or in rare cases IEnumerable<T> for properties or return values representing read-only collections.

public ReadOnlyCollection<Session> Sessions { get; }

In general, prefer ReadOnlyCollection<T>.

Regarding LINQ, the guidelines, which were created for .NET 3.5, are clear but not (imo) entirely convincing in the justification:

The review body made an explicit decision that LINQ should not change this guideline ["Do not return IEnumerator<T>, except as the return type of a GetEnumerator method"]. Your callers can end up with a clumsy object model if they choose not to use LINQ or a language that does not support it.

fatcat1111
Are those the pre-.NET 3.5 guidelines? Wondering if LINQ makes me drift toward IEnumerable<T>.
Mike Two
Lazy eval + LINQ makes me think more about IEnumerable<T>
Matthew Whited
When using 'IEnumerable<T>' how does the consumer of my class use it? Just explict cast back to 'List<T>'?
Nate Bross
Very interesting - I wonder why ReadOnlyCollection is preferred over IEnumerable in the read-only case?
Mathias
The book is a year old... I tend to lean towards IList<T> or IEnumerable<T>
Chuck Conway
These are .NET 3.5 guidelines. I've updated the body to include their comment on LINQ.
fatcat1111
A: 

It ultimately depends on what you want to do with the data being returned. Remember that IEnumerable implies (by that I mean forces) you to access the data in a sequential manner. You can't add to it, alter it, and you can't access an item at a specific point in the array.

IList doesn't have this problem, but you have to provide additional functionality to implement it. If you inherit from a .net object, you might not have to worry about it, but it really depends on how you are creating the object.

Each have their trade offs and there is no one to always default to.

Kevin
You don't have to gather all the data before returning an `IList`. In fact, I often use `IList` when I'd like to provide random access and a `Count` property, but don't want to spend time collecting the data up front. The consumer might only want the 5th element, for instance, so you don't want to make your five calls to the database to populate items 0–4 (to use your example). It;s easy to write an `IList` implementation that populates itself on demand.
P Daddy
I didn't think about that, but you are right.
Kevin
+2  A: 

If the collection is unordered or doesn't need random access, IEnumerable is correct. If it's a list and you want to expose it as one, then declare the method or property to return IList, but you may well need to return a ReadOnlyCollection wrapper over that collection (either directly or using syntax such as List.AsReadOnly()). I would return IQueryable only if I had some useful overrides.

Steven Sudit
A: 

When writing applications, I don't see any problem with returning a specific generic type, e.g.:

List<myType> MyMethod()
{
  ...
}

In my experience, this is easy for the original developer, and easy for other developers to understand what the original developer intended.

But if you're developing some kind of framework that will be used by other developers, you might want to be more sophisticated - returning an interface, for example.

Tom Bushell
The major problem with this approach is that the interface breaks when the class is updated to use something other than List<T>. The minor problem is that exposing the entire List<T> allows modification by insertion or removal.
Steven Sudit
You're correct, but it's been my experience (in application development at least), that changing the types of collections returned by a class is fairly rare. I'd prefer to pay the penalty of refactoring later on - ONLY IF NEEDED, rather than over design all my collection interfaces up front. Your experience may differ, of course...
Tom Bushell
I do understand the emphasis you're putting on applications, but I think the flexibility that returning the interface allows is valuable even when you're not writing a reusable component, particularly when the app has to be maintained and tweaked.
Steven Sudit