views:

104

answers:

4

When reading a post some points were given without example :

To implement IEnumerable / IEnumerable, you must provide an enumerator :

•If the class is "wrapping" another collection, by returning the wrapped collection's enumerator.

•Via an iterator using yield return.

•By instantiating your own IEnumerator/IEnumerator implementation

(My baby mind interprets it as)

(Point 1)

    If the class is "wrapping" another collection, by returning the
wrapped collection's enumerator.

Will it mean ..

class  StringCollections
{

 //A class is wrapping another collection
  string[]  names=new string {“Jon Skeet”,”Hamish Smith”,
                  ”Marc Gravell”,”Jrista”,”Joren”};

//by returning the wrapped collection’s enumerator

 public IEnumerator GetEnumerator( )
  {
      // What should I return here ?
       //like the following ?
        yield return names[0];
        yield return names[1];
        yield return names[2];
      ....

       (or)
        foreach(string str in names)
        {
            yield return str;
         }                  

  }

}

(Point 2)

•Via an iterator using yield return.(This point was well explained 
 by Marc Gravell)

Point 3

By instantiating your own IEnumerator/IEnumerator<T> implementation*

What does point 3 represent here?,since no example,i did not get that one. Does it mean,i can build custom enumerator ..(right?).My question here is when prebuild enumerator/enumerators are sufficient (as a beginner i should not blindly confirm this) for iterations why should i look after the custom one ?Nice example will clarify my doubt.

Thanks for reading this long winded story and kind response.

+2  A: 

Here's an example: (NOTE: This is simplified, not thread safe example)

public class PersonCollection : IEnumerable
{
    private ArrayList alPers = new ArrayList();
    public IEnumerator GetEnumerator() { return new myTypeEnumerator(this); }
    public class myTypeEnumerator : IEnumerator
    {
        int nIndex;
        PersonCollection pers;
        private int count { get { return pers.alPers.Count; } }
        public myTypeEnumerator(PersonCollection myTypes) 
         { pers = myTypes; nIndex = -1; }
        public bool MoveNext() { return nIndex <= count && ++nIndex < count; }
        // MovePrev() not strictly required
        public bool MovePrev() { return (nIndex > -1 && --nIndex > 0 ); }
        public object Current { get { return (pers[nIndex]); } }
        public void Reset() { nIndex = -1; }
    }
}

EDIT: to fix issue raised by @Joren below related to Move Previous taking index value below -1. When called by framework as part of foreach implementation, MoveNext() would not need this fix because in that case if MoveNext() returns false, enumerator instance will terminate. However, if client manually calls MoveNext(), enumerator would not terminate, so it also needs the fix.

Also NOTE: how you implement the details inside this thing is up to you, and will be dependant on how it internally manages state. For example if the iunternal "bucket" that holds the references was a linked list, instead of an ArrayList, then the MoveNext() might be implemented by just changing a Current field to point to the old current's nextItem property...

Charles Bretana
Thanks for spending your time. :)
Joren
@Joren, bug, corrected MovePrev must compare to zero, not the count. But you don't need top compare index against the "other" end of the range... It can never get less than -1 or greater than count... as as soon as it returns false, the enumeration ends...
Charles Bretana
@Joren, on second thought, I see you're calling Move methods explicitly... and yes, in that case, you are right. But this pattern is designed to be used by the framework when client code uses foreach ... in which case the framework generated IL code is calling MoveNext().. and what you describe can't happen... But if MovePrev() is in there, it should be coded to handle your scenario...
Charles Bretana
I think enumerator code should be robust enough to handle this case. `MovePrev` won't even ever be called by `foreach` at all. If you're including it, it should work appropriately.
Joren
@Joren, I Agree
Charles Bretana
+1  A: 

What you said for 1 would actually be for 2.

For 1, it'd be more like

public IEnumerator GetEnumerator() {
    return names.GetEnumerator();
}

For point 3, it'd mean to make GetEnumerator do some real work: Implementing, by hand, an Enumerator for the collection you defined.

Tordek
Thanks for educating me
+3  A: 

(point 1) You can chain the call of GetEnumerator to other collection enumerator:

public class PrimeNumbers : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        var primes = new List<int> { 2, 3, 5, 7, 11 };
        return primes.GetEnumerator();
    }
}

(point 2) Similar to example in your code

public IEnumerator GetEnumerator()
{
    var primes = new List<int> { 2, 3, 5, 7, 11 };
    foreach (var number in primes)
    {
        yield return number;
    }
}

Or place enumerator with logic:

public class PrimeNumbers : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        for(int i=2; ;i++)
        {
            if(IsPrime(i))
            {
                yield return i;
            }
        }
    }
}

(Point 3) There are not many cases where you want to implement your own Enumerator, but for example you can look for an infinite set of values, e.g. prime numbers:

public class PrimeNumbers : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        return new MyEnumerator();
    }
}

public class MyEnumerator : IEnumerator
{
    private int lastPrimeNumber = 1;

    public bool MoveNext()
    {
        lastPrimeNumber = /* some logic that find the next prime */;
        return true; // There is always next prime
    }

    public void Reset()
    {
        lastPrimeNumber = 1;
    }

    public object Current
    {
        get { return lastPrimeNumber; }
    }
}

A usage example could be:

public void PrintAllPrimes()
{
    var numbers = new PrimeNumbers();

    // This will never end but it'll print all primes until my PC crash
    foreach (var number in numbers)
    {
        Console.WriteLine(number);
    }
}

Pros & Cons I can think of:

  • Point 1: It's the simplest way to enumerate items but it requires knowing all items in advance
  • Point 2: It's readable when there's some logic in the enumeration, it's also lazy so there is no need to compute an item until it's actually requested
  • Point 3: It's simplest to reuse but it is less readable (calculate item in MoveNext but actually return from Current property).
Elisha
Thanks you Elisha for sharing info
A: 

"If the class is "wrapping" another collection, by returning thewrapped collection's enumerator" means:

class StringCollections{
    //A class is wrapping another collection
    string[] names=new string {“Jon Skeet”,”Hamish Smith”,
                                ”Marc Gravell”,”Jrista”,”Joren”};
    public IEnumerator GetEnumerator() { return names.GetEnumerator(); }
}
erikkallen