Question

I want to expose internal list as an iterator so calling method will not be limited to foreach loop but it will call IEnumerator.Current and IEnumerator.MoveNext() whenever it likes. I tried two approaches:

public IEnumerator<string> Iterator
{
    get
    {
        return m_list.GetEnumerator();
    }
}

and

public IEnumerator<string> Iterator
{
    get
    {
        for (int i = 0; i < m_list.Count; i++)
        {
            yield return m_list[i];
        }
        yield break;
    }
}

and both causes test to go OutOfMemoryException in the following test:

[TestMethod]
public void TestMethod1()
{
   var countryCode = "US";
   var countryProvider= new CountryProvider(countryCode);
   var filteredList = new List<string>();

   while(countryProvider.Iterator.MoveNext())
   {
       filteredList.Add(countryProvider.Iterator.Current);
   }

   Assert.IsTrue(filteredEFIs.Count > 0);        
}

When I tried debugger I noticed that every time call goes into MoveNext() it starts counting from scratch and Iterator.Current is always null.

Was it helpful?

Solution

The problem is you create new Enumerator every time your Iterator property is called - so both while loop and Add call get new iterator reset to starting state (and hence Current is null).

Correct code would be

var iterator = countryProvider.Iterator;
while(iterator.MoveNext())
   {
       filteredList.Add(iterator.Current);
   }

I believe it partially caused by convention where property returns "cheap and same" value no matter how often they are called. "GetXXXXX" methods on other hand are expected to return something that may change between calls. You can see that pattern on IEnumerable<T> - which gives you iterator via method (GetEnumerator) encouraging to store the value and use it.

OTHER TIPS

Thanks for explaining problem with instances. I came with a solution that doesn't break caller and controls instances of iterator inside the provider class. Scenario I have assumes caller knows two things - when it starts session and when it wants next item so few ways from here, other keep iterator inside caller (breaks implementation little bit) or cache it locally unless reset on purpose which doesn't break current caller implementations (demonstrated below). Other way is to wrap internal enumerator using the decorator pattern (below below)...

private IEnumerator<string> m_iterator;

public string Iterator
{
    get
    {
        if (this.m_iterator == null)
        {
             this.ResetIterator();
        }

        return this.m_iterator.MoveNext();
    }
}

public void ResetIterator()
{
    this.m_iterator = this.m_internalIterator();
}

private IEnumerator<string> m_internalIterator()
{
    for (int i = 0; i < m_list.Count; i++)
    {
        yield return m_list[i];
    }
    yield break;
}

Via decorator pattern, given my Provider type implements IEnumerator:

private IEnumerator<string> m_iterator;

public bool MoveNext()
{
    if (this.m_iterator == null)
    {
        this.ResetIterator();
    }
    return this.m_iterator.MoveNext();        
}

public string Current
{
    get
    {
        if (this.m_iterator == null)
        {
            this.Reset();
        }
        return this.m_iterator.Current;
    }
}

public void Reset()
{
    this.m_iterator = this.m_internalIterator();
}

private IEnumerator<string> m_internalIterator()
{
    for (int i = 0; i < m_list.Count; i++)
    {
        yield return m_list[i];
    }
    yield break;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top