IEnumerable, IEnumerator, and ForEach

Even though each of the collections in Table 7-1 is implemented differently internally, all except BitVector32 implement the IEnumerable interface. This interface exposes one member method, GetEnumerator(). This method returns a pointer to an object that implements the IEnumerator interface. And the IEnumerator interface exposes member methods that allow all collections to be handled the exact same way if there is a need.

The IEnumerator interface is fairly simple. You call the method MoveNext() to advance the enumerator to the next item in the collection, and then you grab the item out of the Current property. You know you have reached the end of the collection when MoveNext() returns false. If you want to move back the enumerator to the start of the collection, call the Reset() method.

In the languages C# and Visual Basic .NET, you don't really have to worry about the IEnumerable or IEnumerator interfaces because you would normally just use the foreach or for each statement, depending on the language. Because Managed C++ doesn't have a foreach statement, I thought I would try to implement one.

The following code hides all the details of the IEnumerable and IEnumerator interfaces within the ForEach class:

 __gc class ForEach {     IEnumerator *enumerator;     IEnumerable *collection; public:     Boolean foreach (Object **out,  IEnumerable *collection)     {          if (this->collection != collection)          {              this->collection = collection;              this->enumerator = collection->GetEnumerator();          }          Boolean end = this->enumerator->MoveNext();          if (end)              *out = this->enumerator->Current;          else              this->enumerator->Reset();          return end;     } }; 

The two member variables are used to keep track of which collection is currently being enumerated and which object was the last one enumerated.

The foreach() method takes a pointer to a pointer Object and any collection class as parameters. To be more accurate, the second parameter takes any class that implements the IEnumerable interface.

The first thing that the foreach() method does is check to see if the same collection is being enumerated. If it is the first time the class is being enumerated, then the enumerator is retrieved along with the pointer to the collection. The code from here on is just standard enumerator code. This means it just calls the MoveNext() method and then extracts the object from the collection using the Current property. You end the foreach() method by resetting the enumerator when the last object has been retrieved so that the foreach() method can be run again on the same collection, if it happens to be called again before any other collection.

As you can see, to implement the foreach() method for a collection class, you can be completely ignorant of the IEnumerable or IEnumerator interface:

 Int32 ints[] = { 1, 2, 3, 5, 7, 11 }; ForEach &loop = *new ForEach(); Object *val; while ( loop.foreach(&val, /*in*/ dynamic_cast<Array*>(ints))  ) {     Console::WriteLine(val); } 

All you need is a collection, an instance of the ForEach class and, finally, an Object pointer to place the value for each of the iterations. Because you need to loop the foreach() method, you need to place it within a while loop.

Notice the dynamic_cast<Array*>(ints). This syntax is needed because the Array class has the IEnumerable interface while the Int32[] array doesn't. Fortunately, Int32[], as well as any other type of managed array, can be dynamically typecast to the Array class.

There is a catch to the ForEach class. If you want to iterate a foreach within a foreach, you need to create another instance of the ForEach class. If you don't, the inner foreach will overwrite the internal variable of the class and the enumerator will lose its place.

Listing 7-1 shows how to implement a foreach statement using Managed C++. Notice that it doesn't matter what type of collection class you are using. The example uses both an Array and an ArrayList, but it could have used a Stack, Queue, Hashtable, and so on.

Listing 7-1: A Managed C++ foreach Statement

start example
 #using <mscorlib.dll> using namespace System; using namespace System::Collections; __gc class ForEach {     IEnumerator *enumerator,     IEnumerable *collection; public:     ForEach()     {         enumerator = 0;         collection = 0;     }     Boolean foreach (Object **out, IEnumerable *collection)     {         if (this->collection != collection)         {             this->collection = collection;             enumerator = collection->GetEnumerator();         }         Boolean end = enumerator->MoveNext();         if (end)             *out = enumerator->Current;         else             enumerator->Reset();         return end;     } }; void SimpleForEach() {     Int32 ints[] = { 1, 2, 3, 5, 1, 11 };     ForEach &loop = *new ForEach();     Object *val;     Console::WriteLine(S"Simple foreach");     while ( loop.foreach( &val, /*in*/ dynamic_cast<Array*>(ints)) )     {         Console::Write(S"{0} ", val);     }     Console::WriteLine(S"\n"); } void ImbeddedForEach() {     Int32 ints[] = { 1, 2, 3, 4, 5};     ArrayList *nums = new ArrayList(s);     for (Int32 i = 0; i < 5; i++)     {         nums->Add(__box((Char)(i+65)));     }     ForEach &loopOuter = *new ForEach();     ForEach &loopInner = *new ForEach();     Object *val1, *val2;     Console::WriteLine(S"Imbedded foreach");     Console::WriteLine(S"Outer foreach");     while ( loopOuter.foreach( &val1, /*in*/ nums) )     {         Console::WriteLine(val1);         Console::Write(S"\tInner foreach\n\t");         while ( loopInner.foreach( &val2, /*in*/ (Array*)ints ))         {             Console::Write(S"{0} ", val2);         }         Console::WriteLine();     } } Int32 main(void) {     SimpleForEach();     ImbeddedForEach();     return 0; } 
end example

Figure 7-1 shows the results of the ForEach.exe program.

click to expand
Figure 7-1: Results of ForEach.exe

If you find the ForEach class helpful, you might consider extracting it into its own header file and including it at the top of source files that use it. You could also add it to a custom utility library assembly and then reference the assembly with the #using command.

Managed C++ and. NET Development
Managed C++ and .NET Development: Visual Studio .NET 2003 Edition
ISBN: 1590590333
EAN: 2147483647
Year: 2005
Pages: 169 © 2008-2017.
If you may any questions please contact us: