My recent experiments with LINQ have taken me to rediscover the power of the IEnumerable
This is extremely powerful because it means the barrier to entry to play in the LINQ world is very low: Pretty much all typed containers in .NET support IEnumerable
However, the goodness of IEnumerable
- It enables a very simple and useful language construct: The "for each" loop.
- It is a highly restricted interface.
The second property is the key that makes IEnumerable
When you're exposing .NET collections directly as part of your API interface, either as properties or as arguments or returns values of functions, you're encouraged to prefer using one of the base collection interfaces instead of the collection types by tools like FxCop [1] (except if you're using arrays directly). So, for example, instead of using List
This is a good practice because it gives you some flexibility in switching collection implementations later on if your original choice wasn't the right one, and I've made a habit of trying to follow it whenever I remember to do so (and FxCop is always there to remind me, as well). However, for many scenarios, even the base interfaces are way to open. For a given scenario, you really have to ask yourself: Do I really need the full capabilities expressed by an interface like IList
For collection properties, I would say that most of the time you do actually need them, so I won't consider them here. However, for method arguments and return values, that's actually more debatable. A lot of scenarios don't require all those capabilities, and indeed, just the capability to iterate over the collection is more than enough! Those are the scenarios were you will want to restrict your public interface and just use IEnumerable
Method Arguments
If all you want to do is walk over a collection inside one of your methods, then by all means you'll want to restrict your interface and ask for an IEnumerable
void DoSomething(IEnumerable<X> list) {
}
As expressed, the interface for the method presents a clearer intent. For the method implementor, it says: All you're supposed to do with this collection is iterate over it. For the method user, it gives him a lot more flexibility in choosing what he passes in, and gives him a clear idea of what the method does with the collection.
This is possibly the case where you more often will want the restricted interface because you, as the developer writing the method, have full control and knowledge about the intent.
Method Return Values
If you're defining a method that will return a collection, think what you expect the user to do with the collection you return. Normally, returning the full collection interface here is the right thing to do because if you're returning a "free flying" (i.e. detached) collection, you want your client to be able to do with it whatever it pleases after that.
However, in some scenarios the opossite is true. Sometimes you get to define a method on which you'll be the consumer and not the implementor. For example, if you're defining an interface to act as an extension hook into your own library/application, then you get to design that interface and your own code will consume it, but someone else is going to be implementing it. In those cases, then you can apply the same reasoning as the "Method Arguments" case above! Since you're the consumer and you have intimate knowledge of what you'll do with the collection you get, you can restrict it to IEnumerable
public IEnumerable<X> GetAll(){
}
One of the nice side effects of having a method defined as returning IEnumerable
[1] Actually, FxCop does give you a choice: Either use the collection interfaces, or use the collection types in System.Collections.ObjectModel.
[2] It might not be evident at first look, but you can define iterator methods as returning either an IEnumerator
Technorati: LINQ, IEnumerable, Iterators