Generic Enumerators

Generic Enumerators

Nongeneric enumerable objects and enumerators are oblique. There is a lack of type specialty, which leads to performance problems and other issues. You can implement enumerable objects and enumerators using generic interfaces, which avoid some of the problems mentioned earlier. Implement the IEnumerable<T> interface for generic enumerable objects. For generic enumerator objects, implement the IEnumerator<T> interface. Both IEnumerable<T> and IEnumerator<T> are generic interfaces found in the .NET FCL in the System.Collections .Generic namespace. IEnumerable<T> and IEnumerator<T> inherit their nongeneric counterparts IEnumerable and IEnumerator, respectively. This means that generic enumerable objects and enumerators are available generically or nongenerically.

IEnumerable<T> Interface

Generic enumerable objects implement the IEnumerable<T> interface. This is the IEnumerable<T> interface:

public interface IEnumerable<T> : IEnumerable {
    IEnumerator<T> GetEnumerator();
}

As shown, IEnumerable<T> inherits IEnumerable, which is the nongeneric version of the same interface. This includes a nongeneric version of the GetEnumerator method. Enumerators inheriting IEnumerable<T> must therefore implement a generic and nongeneric GetEnumerator method. The two GetEnumerator methods differ in return type only. As you know, the return type alone is insufficient for overloading a method. To prevent ambiguousness, one of the GetEnumerator methods must have explicit interface member implementation.

This is sample code of the GetEnumerator methods for a generic enumerable object. (The nongeneric version of GetEnumerator is implemented explicitly.)

public IEnumerator<T> GetEnumerator() {
    return new Enumerator<T>(this);
}

IEnumerator IEnumerable.GetEnumerator() {
    return new Enumerator<T>(this);
}

The generic version of GetEnumerator naturally returns a generic enumerator, which implements the IEnumerator<T> interface.

IEnumerator<T> Interface

Generic enumerators implement the IEnumerator<T> interface, shown here:

public interface IEnumerator<T>: IDisposable, IEnumerator {
    T Current {get;}
}

Current is a read-only property and is the only direct member of the IEnumerator<T> generic interface. The remaining members are inherited from the IDisposable and IEnumerator interfaces. The IDisposable interface defines generic enumerators as disposable. This requires implementing the IDisposable.Dispose method. The IEnumerator interface adds the nongeneric enumerator interface. The MoveNext and Reset methods do not require a generic implementation and are therefore not defined in the generic portion of the interface. A second Current property, a nongeneric version, is inherited from the IEnumerable interface. Therefore, IEnumerator has overloaded Current properties, where both should be implemented in the enumerator.

This is sample code of a generic and nongeneric implementation of the Current property. The nongeneric Current property delegates to the generic version.

public __T Current {
     get {
         if(oThis.version != version) {
             throw new InvalidOperationException(
                 "Collection was modified");
         }

         if(cursor>(oThis.items.Length-1)) {
             throw new InvalidOperationException(
                 "Enumeration already finished");
          }
         if(cursor == -1) {
             throw new InvalidOperationException(
                 "Enumeration not started");
          }
          return oThis.items[cursor];
     }
}

object IEnumerator.Current {
     get {
         return Current;
     }
}

The Dispose method supports deterministic garbage collection. This method is called explicitly to clean up for an object. In this circumstance, the method is called to clean up resources assigned to an enumerator. Dispose methods of enumerators are most frequently called by iterators, which is the next topic of this chapter. In the Dispose method, set the state of the enumeration to After and perform any necessary cleanup. Some enumerators track the state using a flag (an enumeration type), where the flag is updated in the Dispose method and elsewhere in the enumerator. The code presented earlier does not employ a state flag. If the cursor is beyond the collection, the After state is assumed. Conversely, a cursor of -1 indicates the Before state. Based on these assumptions, this is one implementation of a Dispose method for an enumerator:

public void Dispose() {
    cursor=oThis.items.Length+1;
}

Generic Enumerator Example (Versioned Collection)

Earlier in the chapter, a version collection model of enumeration was presented. Here is the versioned collection example redone with generic interfaces. The following code also completes some of the partial code presented earlier in this section. In Main, the collection is enumerated in a generic and nongeneric manner. In the second foreach loop, the simple collection is cast to the nongeneric IEnumerable interface. This instructs the foreach statement to call the nongeneric GetEnumerator method, which returns a nongeneric enumerator. The nongeneric enumerator is then used to iterate the simple collection.

Here is the sample code:

using System;
using System.Collections;
using System.Collections.Generic;

namespace Donis.CSharpBook{

    public class Starter{

        public static void Main(){
            SimpleCollection<int> simple=
                new SimpleCollection<int>(
                new int [] {1,2,3,4,5,6,7});

            foreach(int number in simple) {
                Console.WriteLine(number);
            }

            foreach(int number in
                (IEnumerable) simple) {
                Console.WriteLine(number);
            }
        }
    }

    public class SimpleCollection<T>: IEnumerable<T> {

        public SimpleCollection(T[] array) {
            items=array;
            version=1;
        }

        public T this[int index] {
            get {
                return items[index];
            }
            set {
                ++version;
                items[index]=value;
            }
        }

        public IEnumerator<T> GetEnumerator() {
            Console.WriteLine(
                "IEnumerator<T> GetEnumerator()");
            return new Enumerator<T>(this);
        }

        IEnumerator IEnumerable.GetEnumerator() {
            Console.WriteLine(
                "IEnumerator GetEnumerator()");
            return new Enumerator<T>(this);
        }

        private class Enumerator<__T>: IEnumerator<__T>

        {

            public Enumerator(SimpleCollection<__T> obj) {
                 oThis=obj;
                 cursor=-1;
                 version=oThis.version;
            }

            public __T Current {
                get {
                    if(oThis.version != version) {
                        throw new InvalidOperationException(
                            "Collection was modified");
                    }

                    if(cursor>(oThis.items.Length-1)) {
                        throw new InvalidOperationException(
                            "Enumeration already finished");
                    }
                    if(cursor == -1) {
                        throw new InvalidOperationException(
                            "Enumeration not started");
                    }
                    return oThis.items[cursor];
                }
            }

            public void Dispose() {
                cursor=oThis.items.Length+1;
            }

            public bool MoveNext() {
                ++cursor;
                if(cursor>(oThis.items.Length-1)) {
                    return false;
                }
                return true;
            }

            public void Reset() {
                cursor=-1;
            }

            object IEnumerator.Current {
                 get {
                     return Current;
                 }
            }

            private int version;
            private int cursor;
            private SimpleCollection<__T> oThis;
        }


        private T [] items=null;
        private int version;
    }
}