How iterate over custom collection that implements IEnumerator<T>?

91 Views Asked by At

I want to iterate over the values of my custom collection using C#.

I created a collection named MyLinkedList to represent a custom linked list like this

public class MyLinkedList<T> : IEnumerable<T?> where T : IEquatable<T?>
{
    // other methods are removed for simplicity. 

    IEnumerator<T?> IEnumerable<T?>.GetEnumerator()
        => new MyLinkedListNodeEnumerator<T>(_root);

    public IEnumerator GetEnumerator()
        => new MyLinkedListNodeEnumerator<T>(_root);
}

I implemented the IEnumerable<T?> interface, so I can use foreach loop to iterate over the values in my custom collection. Here is the implementation of MyLinkedListNodeEnumerator

public class MyLinkedListNodeEnumerator<T>(MyLinkedListNode<T>? root)
    : IEnumerator<T?> where T : IEquatable<T?>
{
    private readonly MyLinkedListNode<T>? _root = root;

    private MyLinkedListNode<T>? _current = root;

    public object? Current
    {
        get
        {
            var current = _current;

            _current = _current!.Next;

            return current;
        }
    }

    T? IEnumerator<T?>.Current
    {
        get
        {
            var current = _current;

            _current = _current!.Next;

            return current!.Data;
        }
    }

    public void Dispose()
    {

    }

    public bool MoveNext()
        => _current?.Next != null;

    public void Reset()
    {
        _current = _root;
    }
}

Here is a use case example,

var list = new MyLinkedList<int>();
list.InsertFirst(10);
list.InsertNext(20);
list.InsertNext(30);

When I loop over the Enumerator object, I am expecting that each item to be of int type since that is the generic type to create the list. So the foreach is using IEnumerator GetEnumerator() not IEnumerable<T?>.GetEnumerator().

foreach(var item in list) 
{
    // here item is an object of a type MyLinkedListNode<int> 
    // not int as I am expecting.
}

How do I ensure that foreach will use IEnumerable<T?>.GetEnumerator() so I can iterate over int values instead?

1

There are 1 best solutions below

6
poke On BEST ANSWER

Without knowing the specifics of your MyLinkedListNode:

public class MyLinkedListNodeEnumerator<T> : IEnumerator<T>
{
    private MyLinkedListNode<T> _root;
    private MyLinkedListNode<T> _current = null;

    public MyLinkedListNodeEnumerator(MyLinkedListNode<T> root)
        => _root = root;

    public T Current
        => _current.Data;

    object IEnumerator.Current
        => _current.Data;

    public bool MoveNext()
    {
        _current = _current is null ? _root : _current.Next;
        return _current is not null;
    }

    public void Reset()
        => _current = null;

    public void Dispose()
    { }
}

Note that Current is idempotent and should not modify the enumerator. The only way to move the enumerator forward is using MoveNext. In the initial state, the enumerator is supposed to start before the first element which is why _current starts at null. Also, accessing Current without a previous successful MoveNext call is not defined.


In order for your enumerator to be generic by default, you should swap around your implementation of IEnumerable<T>:

public class MyLinkedList<T> : IEnumerable<T>
{
    // …

    // generic version is public without an explicit implementation
    public IEnumerator<T> GetEnumerator()
        => new MyLinkedListNodeEnumerator<T>(_root);

    // explicit implementation on the non-generic one
    IEnumerator IEnumerable.GetEnumerator()
        => new MyLinkedListNodeEnumerator<T>(_root);
}