Introduction

This article demonstrates how to use a foreach loop to iterate over data in an XML file as if the data were stored in a collection of objects. The fundamental concept presented here is that .NET's IEnumerable/IEnumerator mechanism does not necessarily have to be used in conjunction with collection classes.

Background

Suppose that you have an XML file containing some data that you want to show to the user. There are a variety of ways to get that data from the XML file onto the output device. Many of these techniques involve reading the entire file into some data structure and then showing some subset of the cached data. Wouldn't it be nice if you could just "iterate over the XML file" itself and only store the values that you want to show? Wouldn't it be even nicer if you could perform this iteration with a simple foreach loop? Fortunately, the .NET Framework allows for this type of flexibility and elegance via the IEnumerable and IEnumerator interfaces.

Before we look at the code, here is a refresher on how the foreach loop operates. The object which is being iterated over must implement the IEnumerable interface. IEnumerable has one method, GetEnumerator(), which returns an object that implements the IEnumerator interface. IEnumerator has three public members:

A property called Current which returns the "current" item in the enumerated list of values.

A method called MoveNext() which advances the enumerator to the "next" item in the list of values and returns false if there are no more items to iterate over.

A method called Reset() which positions the enumerator "before" the first item in the list of values.

Prior to the foreach mechanism requesting the current item via the Current property, it will invoke the MoveNext() method. Assuming that MoveNext() returns true, Current is invoked and its return value becomes the "local temp" variable within the scope of the foreach loop.

Using the code

The sample project accompanying this article is quite simple. Its purpose is to demonstrate the technique being presented, but it could easily be extended to fit more sophisticated needs. The sample contains an XML file containing customer information, with a very simple schema:

Below is the code that takes certain customers from the XML file and puts them into a ListBox control. The beauty of this is that from the client's perspective (the user of the Customers class), there is no way of knowing that the data is being read in from an XML file on-the-fly. This implementation fact is completely encapsulated.

privatevoid CustomerEnumerationForm_Load(object sender,
System.EventArgs e)
{
//// Here we create a Customers object and implicitly
// use the CustomerEnumerator to iterate
// over all of the customers in the XML file.
// From our perspective here, it seems
// as though the Customers class has a collection
// containing all of the customers. In
// reality, though, the CustomerEnumerator is reading
// in each customer from Customers.xml
// one at a time, as they are requested by the foreach loop.
////// Note, Customers implements IDisposable.
// This is necessary in case the foreach loop is
// exited before iterating over all of the customers
// in the file. You do not have to explicitly
// invoke the Dispose method, however, because a finalizer
// is in place to handle disposing of the
// XMLTextReader if the client code does not.
using( Customers customers = new Customers() )
{
foreach( Customer cust in customers )
if( cust.Orders > 2 )
this.lstCustomers.Items.Add( cust );
}
}

The Customers class represents all of the customers in the file.

publicclass Customers : IEnumerable
{
// When the foreach loop begins, this method is invoked
// so that the loop gets an enumerator to query.
public IEnumerator GetEnumerator()
{
returnnew CustomerEnumerator();
}
}

The CustomerEnumerator class contains the code that reads in the customer data from the XML file.

There is a very dumb Customer class that just holds onto the data extracted from the XML file so that the enumerator has a way to bundle up all of the data concerning a single customer. I won't bother showing it here because there is nothing in the class that is relevant to the topic of this article. It is available in the sample project, though.

Conclusion

While this novel idea might not be the next silver bullet in software development, I hope that you find it helpful or at least interesting. I know that I did, or else I wouldn't have bothered writing an article about it!

Updates

1/9/05 - Fixed FindNextTextElement() to handle situations where element does not contain a TextElement. Also implemented IDisposable on Customers and CustomerEnumerator as well as added a finalizer to CustomerEnumerator. All of those methods were added for the case where the user of the CustomerEnumerator breaks from the foreach loop before iterating over all the elements in the file. There needed to be a way to close the XmlTextReader.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

This customer class does not have the ID, which would seem quite useful in real life. Is there an easy way to get that included in this code ? I looked at it, and don't know how to make the MoveNext acquire the ID and put it somewhere useful for 'Current' where it creates the customer object instance.