Archive of
Previous Articles


If you implement a custom container or collection class, you almost
certainly want to be able to enumerate or iterate its members.
Ordinarily, this is done using the
For Each...Next statement.
However, attempting to simply define a custom collection class
without providing some means for
For Each...Next to operate
results in a compiler error (error BC32023,
"Expression is of
type 'xxxx', which is not a collection type."
).
So
For Each...Next support is not automatic; in order to use
the construct to iterate the members stored in your container, you
must take explicit steps to make your container enumerable.

One choice is to inherit from a collection class that itself implements
support for the
For Each...Next construct. Unfortunately, you
can't inherit from the Visual Basic
Collection class, since it's
marked as non-inheritable. Instead, you can inherit from one of the
collection classes in
System.Collections or
System.Collections.Specialized. The following code, for
example, implements a custom container that derives from the
DictionaryBase class:

Imports Microsoft.VisualBasic
Imports System
Imports System.Collections

Public Class MyCollection
Inherits System.Collections.DictionaryBase

Public Sub Add(key As [String], value As [String])
 Dictionary.Add(key, value)
End Sub
End Class

Public Class UseMyCollection

Public Shared Sub Main()

 Dim myC As New MyCollection

 myC.Add("NY", "New York")
 myC.Add("CA", "California")
 myC.Add("MI", "Michigan")

 For Each d As DictionaryEntry in MyC
    Console.WriteLine(d.Key & ": " & d.Value)
 Next

End Sub

End Class

When the program is compiled and run, it produces the following output:

CA: California
NY: New York
MI: Michigan

Because we've inherited from a class that supports enumeration, we can use
For Each...Next "for free," without having to do anything special.

If none of the existing container classes quite meet our needs, our second
choice is to build a container class from scratch. In this case, we must explicitly
implement support for
For Each...Next. To do this, our custom class
must support two interfaces:
IEnumerable and IEnumerator.
IEnumerable has a single method:

Function GetEnumerator() As IEnumerator

The IEnumerator type returned by
IEnumerable.GetEnumerator has the following three members:

' Returns the Current Data Item
ReadOnly Property Current As Object   

' Returns True if the enumerator moved
' successfully, false otherwise
Function MoveNext() As Boolean        

' Resets the enumerator to point to one
' position before the first element
Sub Reset()                           

In other words, our custom class must implement a GetEnumerator
method that returns an
IEnumerable interface.  In implementing the latter
interface, we must explicitly position and move the emumerator for each
iteration of the
For Each...Next loop. As long as we properly
implement these members,
For Each...Next will handle the mechanics
of calling the appropriate method.

The following code shows a simple implementation of a custom container that
can be enumerated using
For Each...Next:

Imports Microsoft.VisualBasic
Imports System
Imports System.Collections

Public Class NameContainer
Implements IEnumerator
Implements IEnumerable

Dim iCurrent As Integer = 0
Dim sName(10) As String
Dim ptr As Integer = -1
Dim upperbnd As Integer = 0

Public Sub Add(Value As String)
 If upperbnd > UBound(sName) Then
    ReDim Preserve sName(Ubound(sName) + 10)
 End If
 sName(upperbnd) = Value
 upperbnd += 1
End Sub

Public ReadOnly Property Current() As Object _
              Implements IEnumerator.Current
 Get
    Return sName(ptr)
 End Get
End Property

Public Function MoveNext() As Boolean _
              Implements IEnumerator.MoveNext
 ptr += 1
 If ptr < upperbnd Then Return True
End Function

Public Sub Reset() _
         Implements IEnumerator.Reset
 ptr = -1   
End Sub

Public Function GetEnumerator() As IEnumerator _
              Implements IEnumerable.GetEnumerator
 Return Me
End Function
End Class


Public Class TestCollec
Public Shared Sub Main()
 Dim na As New NameContainer

 na.Add("Alaskan Malamute")
 na.Add("Siberian Husky")
 na.Add("Keeshound")
 na.Add("Chinook")

 For Each s As String In na
    Console.WriteLine(s)
 Next
End Sub
End Class

Notice that the IEnumerator interface works by defining a record pointer
to a particular element of an array or collection. When the enumerator is
created by the call to the
GetEnumerator method, MoveNext is called
automatically to position the pointer to the first element of the collection.
Hence, the pointer must be initialized to point to one position before the first
element of the array or collection. In the case of a zero-based array, this means
the pointer must be initialized to have a value of -1.

It's also interesting to note that we can enumerate the members of a class
without using
For Each...Next  as follows:

Dim iArr() As Integer = {1,2,3,4,5}

Dim iEnum As IEnumerator = iArr.GetEnumerator()

Do While iEnum.MoveNext
Console.WriteLine(iEnum.Current)
Loop

Interestingly, this is precisely the code generated by the compiler when it
encounters the
For Each...Next construct, as the following output
from ILDasm shows:

IL_001e:  callvirt   instance class
[mscorlib]System.Collections.IEnumerator
[mscorlib]System.Array::GetEnumerator()
IL_0023:  stloc.1
IL_0024:  br.s       IL_0036
IL_0026:  ldloc.1
IL_0027:  callvirt   instance object
[mscorlib]System.Collections.IEnumerator::get_Current()
IL_002c:  call       object
[mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::G
etObjectValue(object)
IL_0031:  call       void
[mscorlib]System.Console::WriteLine(object)
IL_0036:  ldloc.1
IL_0037:  callvirt   instance bool
[mscorlib]System.Collections.IEnumerator::MoveNext()
Howling Wolf Consulting Services

Implementing an Enumerable Container
with Visual Basic .NET