tags:

views:

139

answers:

4

i have always thought that returning Arrays were better than lists when having a public API but it seems now there are all these functions on lists that are available through LINQ, etc.

Has the best practice changed here for returning collections of primitives or objects?

for example:

Order[] GetOrders();
List<Order> GetOrders();
IEnumerable<Order> GetOrders();
IQueryable<Order> Get Orders();
+5  A: 

I think the most commonly used type is IEnumerable<T>

  • With a IEnumerable<T> return type you can use the yield keyword and this enables delayed enumeration and execution of your method. (A common complaint for example is that the System.IO.Path.GetFiles() does NOT return a IEnumerable<T> but returns an Array which means that when you call the method all items need to be enumerated regardless if you need them or not. - You have the same disadvantage with List<T>)
  • Most LINQ extension methods work on an IEnumerable<T>
  • The IEnumerable<T> return type doesn't assume anything specific about the caller. If the caller needs a list or array they can always create one.
  • IEnumerable<T> is implemented by List<T> and Array and it is therefore easy to change the implementation of your method and still support the previous return type.
Patrick Klug
Just make sure you don't allow someone to cast your `IEnumerable<T>` to, for example, a `List<T>` and change it if that would break an invariant of the class. See my answer for more details.
Drew Noakes
+5  A: 

Do you want the collection to be immutable? IEnumerable<T> Mutable? IList<T>

Do you need indexes? IList<T>

With a list or array, the API consumer has full capability to add, remove, delete, clear, etc.

With an IEnumerable<T>, the API consumer gets a "bag" of items, with no particular order, no index, and no methods to modify the collection.

There are some limited circumstances in which you might want to return an IQueryable<T>, but they are often specific to building queries piecemeal.

In general, I'd default to IEnumerable<T>. I might use a List<T> as a backing field, but only want to allow the user to Add(), for example -- not to remove, clear, or anything else, so I declare a public void Add<T>(T item) that just adds the item to the backing field list.

Jay
Don't forget about ICollection<T> if you don't need indexes. IList<T> implements ICollection<T> and just adds an indexer, IndexOf(T item), Insert(int index, T item) and RemoveAt(int index).
Chris Shouts
+1  A: 

In addition to what the others have said I want to add that you should never return IQueryable unless you're retrieving the objects from some remote datasource. IQueryable is a query object that will fetch the results on behalf of the caller. When using LINQ, an IQuearyable will usually take an expression tree that will be translated for use by some other system (e.g. Sql Server).

See http://weblogs.asp.net/fredriknormen/archive/2008/12/01/returning-iqueryable-lt-t-gt.aspx for more details.

Rodrick Chapman
+2  A: 

As I generally only return immutable (unmodifiable) objects from properties/methods, this answer assumes you want to do the same.

Don't forget about ReadOnlyCollection<T> which returns an immutable collection that can still be accessed by index.

If you're using IEnumerable<T> and releasing your type into the uncontrollable wilderness, be wary of this:

class MyClass {
    private List<int> _list;
    public IEnumerable<int> Numbers {
        get { return _list; }
    }
}

As a user could do this and mess up the internal state of your class:

var list = (List<int>)myClass.Numbers;
list.Add(123);

This would violate the read-only intention for the property. In such cases, your getter should look like this:

    public IEnumerable<int> Numbers {
        get { return new ReadOnlyCollection<int>(_list); }
    }

Alternatively you could call _list.ToReadOnly(). I wrote it out in full to show the type.

That will stop anyone modifying your state (unless they use reflection, but that's really hard to stop unless you build immutable collections like those use in many functional programming languages, and that's a whole other story).

If you're returning read only collections, you're better off declaring the member as ReadOnlyCollection<T> as then certain actions perform faster (getting count, accessing items by index, copying to another collection).

Personally I'd like to see the framework include and use an interface like this:

public interface IReadOnlyCollection<T> : IEnumerable<T>
{
    T this[int index] { get; }
    int Count { get; }
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    int IndexOf(T item);
}

You can get all these functions using extension methods on top of IEnumerable<T> but they're not as performant.

Drew Noakes