Search code examples
c#treeiteratorhierarchyienumerator

Iterator for hierarchical class


I have a C# class to define a hierarchy (much more complicated than the example below). The class has a Parent and possibly Children of the same class.

I am trying to write an iterator for the top level object that will allow me to access all the contents in a foreach loop.

class Node
{
    public Node Parent { get; private set; }
    public bool HasParent { get { return (Parent != null); } }
    public string Name { get; private set; }
    public bool IsAnimal { get; set; }
    public bool IsCar { get; set; }
    public List<Node> Children { get; private set; }
    public bool HasChildren { get { return (Children != null); } }

}

Want to access like this:

foreach (Node myNode in TopNode.Contents)

It would be nice to have multiple iterators to traverse different types of Children like:

foreach (Node myNode in TopNode.Animals)

or

foreach (Node myNode in TopNode.Cars)

Solution

  • Add this method to the Node class:

    public IEnumerable<Node> DescendantsAndSelf()
    {
        yield return this;
        if (Children != null) {
            foreach (Node child in Children) {
                foreach (Node node in child.DescendantsAndSelf()) {
                    yield return node;
                }
            }
        }
    }
    

    And you don't need different iterators for different types of nodes. Just use .Where(...)

    var allAnimals = myTopNode.DescendantsAndSelf()
        .Where(n => n.IsAnimal);
    

    If you take to heart @LasseVågsætherKarlsen's suggestion and derive different nodes types from an abstract base class Node, then you can get animals typed as Animal like this:

    IEnumerable<Animal> allAnimals = myTopNode.DescendantsAndSelf()
        .OfType<Animal>();
    

    You could also declare Children as:

    public List<Node> Children { get; } = new List<Node>();
    

    Like this, Children would never be null and HasChildren would be implemented as:

    public bool HasChildren => Children.Count > 0;
    

    See: