freinn freinn - 3 months ago 9
C# Question

How to implement the IEnumerator<T> interface for the composite pattern?

I have a collection of objects following the composite pattern. They form a tree structure that I want to traverse with an IEnumerator. I'm translating some java code from the book "Head First Design Patterns". I've implemented two classes that implement the IEnumerator interface: CompositeIterator and NullIterator.

This is the java code I want to translate to C#.

Also, this is my wanted implementation of the Waitress class, when I only have to call MoveNext() and Current to traverse the whole tree structure.

Now my code is not entering the while loop that involves the iterator, and I want to print the vegetarian MenuItem objects on the console.

Here is my code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Iterator
{
class Program
{
static void Main(string[] args)
{
MenuComponent pancakeHouseMenu = new Menu("PANCAKEHOUSE MENU", "Breakfast");
MenuComponent dinerMenu = new Menu("DINER MENU", "Lunch");
MenuComponent cafeMenu = new Menu("CAFE MENU", "Dinner");
MenuComponent dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");

MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");

allMenus.Add(pancakeHouseMenu);
allMenus.Add(dinerMenu);
allMenus.Add(cafeMenu);

pancakeHouseMenu.Add(new MenuItem("K&B Pancake breakfast", "pancakes with scrambled eggs, and toast", true, 2.99));
pancakeHouseMenu.Add(new MenuItem("Regular Pancake breakfast", "pancakes with fried eggs, sausage", false, 2.99));

dinerMenu.Add(new MenuItem("Veggie burguer and air fries", "Veggie burguer on a whole wheat bun, lettuce, tomato and fries", true, 3.99));
dinerMenu.Add(new MenuItem("Soup of the day", "Soup of the day with a side salad", false, 3.69));

dinerMenu.Add(dessertMenu);

dessertMenu.Add(new MenuItem("Apple pie", "Apple pie with a flakey crust, topped with vanilla ice cream", true, 1.59));

cafeMenu.Add(new MenuItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99));
cafeMenu.Add(new MenuItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99));

Waitress waitress = new Waitress(allMenus);
waitress.PrintVegetarianMenu();
}
}

class Waitress
{
private MenuComponent AllMenus { get; set; }

public Waitress(MenuComponent allMenus)
{
AllMenus = allMenus;
}

public void PrintMenu()
{
AllMenus.Print();
}

public void PrintVegetarianMenu()
{
CompositeIterator<MenuComponent> iterator = (CompositeIterator<MenuComponent>)AllMenus.CreateIterator();
Console.WriteLine("VEGATARIAN MENU");

// this loop is never entered
while (iterator.MoveNext())
{
Console.WriteLine("inside while loop");
MenuComponent menuComponent = (MenuComponent)iterator.Current;
Console.WriteLine(menuComponent.Name);

try
{
if (menuComponent.Vegetarian)
{
menuComponent.Print();
}
}
catch (NotSupportedException e)
{
Console.WriteLine("Operation not supported.");
}
}
}
}

/*
Methods of MenuComponent class are virtual, because we sometimes want to use the default behavior. The CreateIterator method is abstract.
*/
abstract class MenuComponent
{
// Composite methods
public virtual void Add(MenuComponent menuComponent)
{
throw new NotSupportedException();
}

public virtual void Remove(MenuComponent menuComponent)
{
throw new NotSupportedException();
}

public virtual MenuComponent GetChild(int i)
{
throw new NotSupportedException();
}
// End of composite methods

// Operation methods
public virtual string Name
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}

public virtual string Description
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}

public virtual bool Vegetarian
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}

public virtual double Price
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}

public virtual void Print()
{
throw new NotSupportedException();
}
// End of operation methods

public abstract IEnumerator CreateIterator();
}

public sealed class CompositeIterator<T> : IEnumerator<T> {
private readonly Stack<IEnumerator<T>> Stack = new Stack<IEnumerator<T>>();

public CompositeIterator(IEnumerator<T> initial)
{
Stack.Push(initial);
}

public bool MoveNext()
{
while (Stack.Any())
{
if (!Stack.Peek().MoveNext())
{
Stack.Pop().Dispose();
continue;
}
var tmp = Current as IEnumerable<T>;
if (tmp != null) { Stack.Push(tmp.GetEnumerator()); }
}
return false;
}

public void Reset() { throw new NotSupportedException(); }

public T Current => Stack.Peek() != null ? Stack.Peek().Current : default(T);

object IEnumerator.Current => Current;

public void Dispose()
{
if (!Stack.Any()) { return; }
try {
foreach (var x in Stack) {
x.Dispose();
}
} catch { }
}
}

public sealed class NullIterator<T> : IEnumerator<T> {
public NullIterator() {}

public bool MoveNext()
{
return false;
}

public void Reset() { throw new NotSupportedException(); }

public T Current
{
get
{
return default(T);
}
}

object IEnumerator.Current => Current;

public void Dispose()
{
return;
}
}

// This is a tree leaf
class MenuItem : MenuComponent
{
public override string Name { get; set; }
public override string Description { get; set; }
public override bool Vegetarian { get; set; }
public override double Price { get; set; }

public MenuItem(string name, string description, bool vegetarian, double price)
{
Name = name;
Description = description;
Vegetarian = vegetarian;
Price = price;
}

public override void Print()
{
Console.Write(" " + Name);
if (Vegetarian)
{
Console.Write("(v)");
}
Console.Write(", " + Price);
Console.Write(" -- " + Description);
}

public override IEnumerator CreateIterator()
{
return new NullIterator<MenuItem>();
}
}

// This is a tree node
class Menu : MenuComponent
{
public List<MenuComponent> MenuComponents;
public override string Name { get; set; }
public override string Description { get; set; }

public Menu(string name, string description)
{
Name = name;
Description = description;
MenuComponents = new List<MenuComponent>();
}

public override void Add(MenuComponent menuComponent)
{
MenuComponents.Add(menuComponent);
}

public override void Remove(MenuComponent menuComponent)
{
MenuComponents.Remove(menuComponent);
}

public override MenuComponent GetChild(int i)
{
return MenuComponents[i];
}

// we have to use recursion to print all the hierarchy
public override void Print()
{
Console.Write("\n" + Name);
Console.WriteLine(", " + Description);
Console.WriteLine("--------------");

IEnumerator iterator = MenuComponents.GetEnumerator();

while(iterator.MoveNext())
{
MenuComponent menuComponent = (MenuComponent)iterator.Current;
menuComponent.Print();
Console.Write("\n");
}
}

public override IEnumerator CreateIterator()
{
return new CompositeIterator<MenuComponent>(MenuComponents.GetEnumerator());
}
}
}

Answer

After contacting people on the C# freenode IRC channel, one of them gave me a solution better than the one in the book and the answer I got here. This is the code, and with this answer I hope is clear what I wanted:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Iterator
{
  class Program
  {
    static void Main(string[] args)
    {
      MenuComponent pancakeHouseMenu = new Menu("PANCAKEHOUSE MENU", "Breakfast");
      MenuComponent dinerMenu = new Menu("DINER MENU", "Lunch");
      MenuComponent cafeMenu = new Menu("CAFE MENU", "Dinner");
      MenuComponent dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");

      MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");

      allMenus.Add(pancakeHouseMenu);
      allMenus.Add(dinerMenu);
      allMenus.Add(cafeMenu);

      pancakeHouseMenu.Add(new MenuItem("K&B Pancake breakfast", "pancakes with scrambled eggs, and toast", true, 2.99));
      pancakeHouseMenu.Add(new MenuItem("Regular Pancake breakfast", "pancakes with fried eggs, sausage", false, 2.99));

      dinerMenu.Add(new MenuItem("Veggie burguer and air fries", "Veggie burguer on a whole wheat bun, lettuce, tomato and fries", true, 3.99));
      dinerMenu.Add(new MenuItem("Soup of the day", "Soup of the day with a side salad", false, 3.69));

      dinerMenu.Add(dessertMenu);

      dessertMenu.Add(new MenuItem("Apple pie", "Apple pie with a flakey crust, topped with vanilla ice cream", true, 1.59));

      cafeMenu.Add(new MenuItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99));
      cafeMenu.Add(new MenuItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99));

      cafeMenu.Add(dessertMenu);

      Waitress waitress = new Waitress(allMenus);
      waitress.PrintVegetarianMenu();
    }
  }

  class Waitress
  {
    private MenuComponent AllMenus { get; set; }

    public Waitress(MenuComponent allMenus)
    {
      AllMenus = allMenus;
    }

    public void PrintMenu()
    {
      AllMenus.Print();
    }

    public void PrintVegetarianMenu()
    {
      Console.WriteLine("VEGATARIAN MENU");

      foreach (MenuComponent menuComponent in AllMenus)
      {
        try
        {
          if (menuComponent.Vegetarian)
          {
            menuComponent.Print();
            Console.Write("\n");
          }
        }
        catch (NotSupportedException)
        {
          Console.WriteLine("Operation not supported.");
        }
      }
    }
  }

  abstract class MenuComponent : IEnumerable<MenuComponent>
  {
    // Composite methods
    public abstract void Add(MenuComponent menuComponent);

    public abstract void Remove(MenuComponent menuComponent);

    public abstract MenuComponent GetChild(int i);
    // End of composite methods

    // Operation methods
    public virtual string Name { get; set; }

    public virtual string Description { get; set; }

    public virtual bool Vegetarian { get; set; }

    public virtual double Price { get; set; }

    public abstract void Print();

    public abstract IEnumerator<MenuComponent> GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  }

  /// This is a tree leaf
  class MenuItem : MenuComponent
  {
    public MenuItem(string name, string description, bool vegetarian, double price)
    {
      Name = name;
      Description = description;
      Vegetarian = vegetarian;
      Price = price;
    }

    public override void Print()
    {
      Console.Write("  " + Name);
      if (Vegetarian)
      {
        Console.Write("(v)");
      }
      Console.Write(", " + Price);
      Console.Write("     -- " + Description);
    }

    public override IEnumerator<MenuComponent> GetEnumerator()
    {
      yield break;
    }

    public override void Add(MenuComponent menuComponent)
    {
      throw new NotSupportedException();
    }

    public override void Remove(MenuComponent menuComponent)
    {
      throw new NotSupportedException();
    }

    public override MenuComponent GetChild(int i)
    {
      throw new NotSupportedException();
    }
  }

  /// This is a tree node
  class Menu : MenuComponent
  {
    private List<MenuComponent> MenuComponents;

    public Menu(string name, string description)
    {
      Name = name;
      Description = description;
      MenuComponents = new List<MenuComponent>();
    }

    public override void Add(MenuComponent menuComponent)
    {
      MenuComponents.Add(menuComponent);
    }

    public override void Remove(MenuComponent menuComponent)
    {
      MenuComponents.Remove(menuComponent);
    }

    public override MenuComponent GetChild(int i)
    {
      return MenuComponents[i];
    }

    // we have to use recursion to print all the hierarchy
    public override void Print()
    {
      Console.Write("\n" + Name);
      Console.WriteLine(", " + Description);
      Console.WriteLine("--------------");

      foreach (MenuComponent menuComponent in MenuComponents)
      {
        menuComponent.Print();
        Console.Write("\n");
      }
    }

    public override IEnumerator<MenuComponent> GetEnumerator()
    {
      var components = new Stack<MenuComponent>(new[] { this });
      while (components.Any())
      {
        MenuComponent component = components.Pop();
        yield return component;
        var menu = component as Menu;
        if (menu != null)
        {
          foreach (var n in menu.MenuComponents) components.Push(n);
        }
      }
    }
  }
}
Comments