Joan Venge
Joan Venge

Reputation: 331490

Is it possible to write multiple iterators for a type in C#?

So for a type like:

CoolCollection<T>

you could have:

foreach (T item in coolCollection)
{
    ...
}

foreach (CoolNode node in coolCollection)
{
    ...
}

If this isn't possible, maybe like foreach2, or some other way to iterate. Often times, I would really like more than 1 way of iterating on a type.

EDIT: Sorry if it wasn't clear. Basically CoolNode is a node that makes CoolCollection. CoolNode has a property called value to return T, but I need another iterator to return only CoolNodes.

EDIT2: I can't do coolCollection.Something to iterate, because CoolNodes are connected via a property called Next, like a LinkedList. So I need to implement 2 iterators.

Upvotes: 1

Views: 994

Answers (5)

Reed Copsey
Reed Copsey

Reputation: 564871

Just make CoolCollection<T> explicitly implement IEnumerable<CoolNode<T>> as well as IEnumerable<T>. (I'm guessing it's really CoolNode<T>, but if not, just take the extra <T> out everywhere.)

This will let you iterate in both manners, although you'll need a cast.

To do this, you'd need something like:

class CoolCollection<T> : ICollection<T>, IEnumerable<CoolNode<T>>
{
    IEnumerator<CoolNode<T>> IEnumerable<CoolNode<T>>.GetEnumerator()
    {
        ///...Do work here...
    }

    IEnumerator<T> GetEnumerator()
    {
        ///...Do work here...
    }
}

Using this would be like so:

foreach (T item in coolCollection)
{
    ...
}


foreach (CoolNode<T> node in (IEnumerable<CoolNode<T>>)coolCollection)
{
    ...
}

The other option would be to expose a property for the "nodes", so you could do:

foreach(var nodes in coolCollection.Nodes)
{ ... }

To implement this, you'd change things around a little bit. You'd need to make a private class that implemented the enumerator... something like:

class CoolCollection<T> : ICollection<T>
{
    private List<CoolNode<T>> nodes;

    IEnumerable<CoolNode<T>> Nodes
    {
        get 
        {
             foreach(var node in this.nodes) { yield return node; }
        }
    }
}

Upvotes: 3

Sam Saffron
Sam Saffron

Reputation: 131182

No, you can't do that. You can not overload your default iterator.

Imagine if you could overload your default iterator.

What would this do? foreach (object o in foo) , there would be no logical way to choose the right iterator.

What you can do is have a second method named ForEach2 that iterates through your collection in a different way. Or you could explicitly implement an interface. Or you could use Linq composition for this kind of stuff.

From a class design perspective:

interface IBar {
   IEnumerator<string> GetEnumerator();
}

class Foo : IBar, IEnumerable<int> {

    // Very bad, risky code. Enumerator implementations, should 
    // line up in your class design. 
    public IEnumerator<int> GetEnumerator()
    {
        yield return 1;
        yield return 2;
        yield return 3;
        yield return 4;
    }

    IEnumerator<string> IBar.GetEnumerator()
    {
        yield return "hello";
    }

    // must be IEnumerable if you want to support foreach 
    public IEnumerable<string> AnotherIterator
    { 
        get {
           yield return "hello2";
        }
    }


    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator(); 
    }

}

LINQ extensions for EachPair

struct Pair<T> { 
    public T First;
    public T Second;
}

static class LinqExtension {
    public static IEnumerable<Pair<T>> EachPair<T>(this IEnumerable<T> input) {
        T first = default(T);
        bool gotFirst = false;
        foreach (var item in input)
        {
            if (!gotFirst)
            {
                first = item;
                gotFirst = true;
            }
            else {
                yield return new Pair<T>() { First = first, Second = item };
                gotFirst = false;
            }
        } 
    }
}

Test code:

class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo(); 

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

        // LINQ composition - a good trick where you want
        //  another way to iterate through the same data 
        foreach (var pair in foo.EachPair())
        {
            Console.WriteLine("got pair {0} {1}", pair.First, pair.Second);
        }

        // This is a bad and dangerous practice. 
        // All default enumerators should be the same, otherwise
        // people will get confused.
        foreach (string str in (IBar)foo)
        {
            Console.WriteLine(str);
        }

        // Another possible way, which can be used to iterate through
        //   a portion of your collection eg. Dictionary.Keys 
        foreach (string str in foo.AnotherIterator)
        {
            Console.WriteLine(str);
        }
    }

Upvotes: 1

Jay Bazuzi
Jay Bazuzi

Reputation: 46546

Take a look at the iterindex snippet. In your class, type iterindex and hit [TAB]. It will help you implement a "Named Iterator and Indexer" pattern.

The result can by used like this:

foreach (var e in myTree.DepthFirstView) // supports iteration
{
    if (e == myTree.DepthFirstView[2]) // supports indexing
    {
        // ...
    }
}

(I wrote this snippet, but I suspect it has never been put to good use. Ever.)

Upvotes: 0

bbmud
bbmud

Reputation: 2688

If CoolCollection implements IEnumerable you can write:

foreach (var item in coolCollection)
{
    ...
}

or if T is CoolNode

foreach (CoolNode node in coolCollection)
{
    ...
}

If you need somehow transform each item to your type, you can use Linq Select operator:

foreach (CoolNode node in coolCollection.Select(item => ConvertToNode(item))
{
    ...
}

Upvotes: 0

bytebender
bytebender

Reputation: 7491

If I understand the question correctly...

You could do it similar to the some of the other collection objects do it:

for example:

foreach (int key in IDictionary.Keys)
{

}

foreach (object value in IDictionary.Values)
{

}

But I don't think there is a way to do exactly the way you have it written...

Upvotes: 2

Related Questions