Over the next few articles, we'll aggregate the useful information about IEnumerable<T> from these other sessions, and then we'll use them to our advantage. The articles and code samples will be collected here: Next, Please - A Closer Look at IEnumerable.
The IEnumerable<T> Interface
IEnumerable<T> is a .NET Interface (for more information on Interfaces, refer to IEnumerable, ISaveable, IDontGetIt: Interfaces in .NET). As far as interfaces go, it's pretty straight forward -- it only contains 1 method (well, technically two methods that do almost the same thing):
- IEnumerator GetEnumerator()
- IEnumerator<T> GetEnumerator()
As you might imagine, it would probably be useful to look at the IEnumerator<T> interface as well. This is a little more complex, but still pretty straight forward:
- void Dispose()
- bool MoveNext()
- void Reset()
- object Current
- T Current
We have two separate properties called "Current" to support generic and non-generic properties. IEnumerator<T> (the generic version) also specifies an implementation of IEnumerator (the non-generic version). The IEnumerator property of is type "object", and the IEnumerator<T> property is of type "T".
We'll be creating classes that implement IEnumerable<T> and IEnumerator<T> in Parts 3 and 4. So we'll have plenty of time to take a deeper dive into these interfaces.
The Iterator Pattern
The IEnumerable<T> interface is a description of the Iterator Pattern -- one of the Gang of Four (GoF) patterns. For more information on this pattern, refer to Learn the Lingo: Design Patterns. We'll be taking a look at the sample code from that session here.
The GoF description of the Iterator pattern:
"Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation." [Gamma, Helm, Johnson, and Vlissides. Design Patterns. Addison-Wesley, 1995.]This simply means a way to get all of the items out of a collection one at a time -- "Next, Please!" This is similar to a television remote control where we can press "Channel Up" to get the next available channel.
It turns out that pretty much every collection in .NET implements the IEnumerable interface. A favorite collection of mine, List<T>, implements IEnumerable<T>.
If we look at the "Iterator" project from the "DesignPatterns" solution (from the link above), we see a very simple block of code:
This method is in a button-click event of a WPF application. The People.GetPeople() method is used to populate a List<Person> object with some predefined values. Since List<T> implements IEnumerable<T>, we know that it must have a GetEnumerator() method.
By calling GetEnumerator on our people object, we get a variable that contains that enumerator. This is where we'll use 2 members of the IEnumerator<T> interface. MoveNext() will move to the next item in the collection (the first time it is called, it will move to the first item in the collection). MoveNext() will return "true" as long as there is a next item. When it gets to the end of the collection, it will return false. The "Current" property will be the item that is currently pointed to by the enumerator. Since this is a generic collection, the current item will be of type "Person".
By using the while loop, we can iterate through all of the items in the collection and use them to populate a ListBox. Here's the output:
Not all that exciting, but we're just demonstrating the pattern here. As we can see, the combination of IEnumerable<T> and IEnumerator<T> describes the Iterator pattern pretty well -- allowing us to traverse the items in a collection without concern for the collection itself or how it handles things internally.
The Real World: foreach
If the code in the previous example looks odd to you, you're not alone. Generally, we don't interact with enumerators quite so directly; we let the .NET compiler do it. Fortunately, we have "foreach" to hide the complexity for us.
If we look up "foreach" in .NET Help (more specifically "foreach keyword [C#]"), we see an easier way to loop through items in a collection. In the description, we see that the foreach loop will operate on any object that implements IEnumerable or IEnumerable<T>. This is great for us; we can get rid of the explicit use of the enumerator that we had above and write code that looks like this:
Notice that we do not have to get an instance of the enumerator, nor do we have to deal with the "Current" object. Instead, we just use the "foreach" loop. Behind the scenes, the compiler will turn this into code similar to what we had above (calling GetEnumerator(), MoveNext(), etc.), but we no longer need to worry about this in our code. The "p" variable corresponds to the "Current" property (and it's still a strongly-typed "Person"), and "MoveNext()" gets called for us on each iteration of the loop.
As we saw, "foreach" will only work on an object that implements IEnumerable or IEnumerable<T>. Have you ever tried it with a different type? If you try to use "foreach" with an integer type, then you will get a compiler error (which will remind you that the object must implement IEnumerable).
If you try to use "foreach" with a string type, it works! This is because the string class in .NET implements IEnumerable<char>. So, you can actually use "foreach" to iterate through the characters of a string. This can be useful for certain types of string processing.
Today we saw the members that are included in the IEnumerable<T> interface (and IEnumerator<T>). We also saw how IEnumerable<T> is actually a description of the Iterator Pattern as described by the Gang of Four. Finally, we saw how we can use the "foreach" loop with a class that implements IEnumerable or IEnumerable<T>.
Next time, we'll take a look at the extensions methods that are available for IEnumerable<T>. These extension methods are what make LINQ possible (and fun!). What we'll see is that we can use any of these LINQ extension methods with any class that implements the IEnumerable<T> interface.
Post a Comment