A generic tree of nodes, the easy way!

Today I saw a Stackoverflow-post about a genaral solution for tree and nodes. It reminds me of a solution I created to store departments in tree. I was surprised that no standard .NET collection does exactly what I need, so I created my own Node. It has properties like:

  • Children
  • Ancestors
  • Descendants
  • Siblings
  • Level (of the node)
  • Root
  • Parent
  • Etc.

It also made it easy to create a tree of nodes from items a flat list (or records from a database).

The queries that are possible with this node/tree are suprisingly fast. It is so fast because it stores a reference to both the children and the parent.

You can add values (or nodes) manually to an other node or use a single static method to convert a flat list of items to a tree! Even if the items of the list are in random order.


      public class Location
      {
          public Location(int id, int? parentId, string name)
          {
              Id = id;
              ParentId = parentId;
              Name = name;
          }

          public int Id { get; set; }
          public int? ParentId { get; set; }
          public string Name { get; set; }

          public override string ToString()
          {
              return Name;
          }
      }
      
      private IEnumerable<Location> GetLocations()
      {
          return new List<Location>
              {
                  //           id, parentID, name
                  new Location(12, 20, "Amsterdam"),
                  new Location(2, 50, "North"),
                  new Location(4, 50, "West"),
                  new Location(5, 50, "East"),
                  new Location(20, 4, "The Netherlands"),
                  new Location(50, null, "Europe"),
                  new Location(7, 4, "Belgium"),
                  new Location(31, 30, "Spain"),
                  new Location(31, 30, "Italy"),
                  new Location(19, 20, "Rotterdam"),
                  new Location(30,50, "South"),
                  new Location(11, 20, "Leiden"),
              };
      }
 
      public void Test()
      {
          var locations = GetLocations();
                   
          // With just one line of code!!
          var rootNodes = Node<Location>.CreateTree(locations, l => l.Id, l => l.ParentId);
                   
          var rootNode = rootNodes.Single(); // Assume that all nodes belong to one tree, otherwise there would be more rootnodes
          var theNetherlands = rootNode.Descendants.Single(n => n.Value.Name == "The Netherlands");
       
          // Get the children
          foreach (var node in theNetherlands)
          {
              Console.WriteLine(node.Value.Name); // Amsterdam, Rotterdam, Leiden
          }
       
          // Get the ancestors
          foreach (var node in theNetherlands.Ancestors)
          {
              Console.WriteLine(node.Value.Name); // West, Europe
          }
      }
      

You can also add Nodes the regular way:


      // Use initialiser to add childs
      var world = new Node<string>("World") {"Europe", "Afrika", "America", "Australia"};
      var europe = world[0];
                   
      // Add manually
      var theNetherlands = europe.Add("The Netherlands");
      theNetherlands.Add("Amsterdam");
      theNetherlands.Add("Rotterdam");
                   
      // Combine both methods
      var england = new Node<string>("England") {"London", "Oxford"};
      europe.Add(england);
      

Use the handy Values() extension method if you only need the values of the Nodes enumeration.


      foreach (var value in world.Descendants.Values())
      {
          Console.WriteLine(value);
      }
      

This is the code of the Node class (please note that this class is stripped from code that uses other classes out of the scope of this blog, like Null-checks):


      using System;
      using System.Collections;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
       
      namespace BlogConsole.Nodes
      {
        public class Node<T> : IEqualityComparer, IEnumerable<T>, IEnumerable<Node<T>>
        {
          public Node<T> Parent { get; private set; }
          public T Value { get; set; }
          private readonly List<Node<T>> _children = new List<Node<T>>();
   
          public Node(T value)
          {
              Value = value;
          }
   
          public Node<T> this[int index]
          {
              get
              {
                  return _children[index];
              }
          }
   
          public Node<T> Add(T value, int index = -1)
          {
              var childNode = new Node<T>(value);
              Add(childNode, index);
              return childNode;
          }
   
          public void Add(Node<T> childNode, int index = -1)
          {
              if (index < -1)
              {
                  throw new ArgumentException("The index can not be lower then -1");
              }
              if (index > Children.Count()-1)
              {
                  throw new ArgumentException("The index ({0}) can not be higher then index of the last iten. Use the AddChild() method without an index to add at the end".FormatInvariant(index));
              }
              if (!childNode.IsRoot)
              {
                  throw new ArgumentException("The child node with value [{0}] can not be added because it is not a root node.".FormatInvariant(childNode.Value));
              }
   
              if (Root == childNode)
              {
                  throw new ArgumentException("The child node with value [{0}] is the rootnode of the parent.".FormatInvariant(childNode.Value));
              }
   
              if (childNode.SelfAndDescendants.Any(n => this == n))
              {
                  throw new ArgumentException("The childnode with value [{0}] can not be added to itself or its descendants.".FormatInvariant(childNode.Value));
              }
              childNode.Parent = this;
              if (index == -1)
              {
                  _children.Add(childNode);
              }
              else
              {
                  _children.Insert(index, childNode);
              }
          }
   
          public Node<T> AddFirstChild(T value)
          {
              var childNode = new Node<T>(value);
              AddFirstChild(childNode);
              return childNode;
          }
   
          public void AddFirstChild(Node<T> childNode)
          {
              Add(childNode, 0);
          }
   
          public Node<T> AddFirstSibling(T value)
          {
              var childNode = new Node<T>(value);
              AddFirstSibling(childNode);
              return childNode;
          }
   
          public void AddFirstSibling(Node<T> childNode)
          {
              Parent.AddFirstChild(childNode);
          }
          public Node<T> AddLastSibling(T value)
          {
              var childNode = new Node<T>(value);
              AddLastSibling(childNode);
              return childNode;
          }
   
          public void AddLastSibling(Node<T> childNode)
          {
              Parent.Add(childNode);
          }
   
          public Node<T> AddParent(T value)
          {
              var newNode = new Node<T>(value);
              AddParent(newNode);
              return newNode;
          }
           
          public void AddParent(Node<T> parentNode)
          {
              if (!IsRoot)
              {
                  throw new ArgumentException("This node [{0}] already has a parent".FormatInvariant(Value), "parentNode");
              }
              parentNode.Add(this);
          }
   
          public IEnumerable<Node<T>> Ancestors
          {
              get
              {
                  if (IsRoot)
                  {
                      return Enumerable.Empty<Node<T>>();
                  }
                  return Parent.ToIEnumarable().Concat(Parent.Ancestors);
              }
          }
   
          public IEnumerable<Node<T>> Descendants
          {
              get
              {
                  return SelfAndDescendants.Skip(1);
              }
          }
   
          public IEnumerable<Node<T>> Children
          {
              get
              {
                  return _children;
              }
          }
   
          public IEnumerable<Node<T>> Siblings
          {
              get
              {
                  return SelfAndSiblings.Where(Other);
   
              }
          }
   
          private bool Other(Node<T> node)
          {
              return !ReferenceEquals(node, this);
          }
   
          public IEnumerable<Node<T>> SelfAndChildren
          {
              get
              {
                  return this.ToIEnumarable().Concat(Children);
              }
          }
   
          public IEnumerable<Node<T>> SelfAndAncestors
          {
              get
              {
                  return this.ToIEnumarable().Concat(Ancestors);
              }
          }
   
          public IEnumerable<Node<T>> SelfAndDescendants
          {
              get
              {
                  return this.ToIEnumarable().Concat(Children.SelectMany(c => c.SelfAndDescendants));
              }
          }
   
          public IEnumerable<Node<T>> SelfAndSiblings
          {
              get
              {
                  if (IsRoot)
                  {
                      return this.ToIEnumarable();
                  }
                  return Parent.Children;
   
              }
          }
   
          public IEnumerable<Node<T>> All
          {
              get
              {
                  return Root.SelfAndDescendants;
              }
          }
   
   
          public IEnumerable<Node<T>> SameLevel
          {
              get
              {
                  return SelfAndSameLevel.Where(Other);
   
              }
          }
   
          public int Level
          {
              get
              {
                  return Ancestors.Count();
              }
          }
   
          public IEnumerable<Node<T>> SelfAndSameLevel
          {
              get
              {
                  return GetNodesAtLevel(Level);
              }
          }
   
          public IEnumerable<Node<T>> GetNodesAtLevel(int level)
          {
              return Root.GetNodesAtLevelInternal(level);  
          }
   
          private IEnumerable<Node<T>> GetNodesAtLevelInternal (int level)
          {
              if (level == Level)
              {
                  return this.ToIEnumarable();
              }
              return Children.SelectMany(c => c.GetNodesAtLevelInternal(level));  
          }
   
          public Node<T> Root
          {
              get
              {
                  return SelfAndAncestors.Last();
              }
          }
   
          public void Disconnect()
          {
              if (IsRoot)
              {
                  throw new InvalidOperationException("The root node [{0}] can not get disconnected from a parent.".FormatInvariant(Value));
              }
              Parent._children.Remove(this);
              Parent = null;
          }
   
          public bool IsRoot
          {
              get { return Parent == null; }
          }
   
          IEnumerator<T> IEnumerable<T>.GetEnumerator()
          {
              return _children.Values().GetEnumerator();
          }
   
          IEnumerator IEnumerable.GetEnumerator()
          {
              return _children.GetEnumerator();
          }
   
          public IEnumerator<Node<T>> GetEnumerator()
          {
              return _children.GetEnumerator();
          }
   
          public override string ToString()
          {
              return Value.ToString();
          }
   
          public static IEnumerable<Node<T>> CreateTree<TId>(IEnumerable<T> values, Func<T, TId> idSelector, Func<T, TId?> parentIdSelector)
              where  TId: struct
          {
              var valuesCache = values.ToList();
              if (!valuesCache.Any())
                  return Enumerable.Empty<Node<T>>();
              T itemWithIdAndParentIdIsTheSame = valuesCache.FirstOrDefault(v => IsSameId(idSelector(v), parentIdSelector(v)));
              if (itemWithIdAndParentIdIsTheSame != null) // Hier verwacht je ook een null terug te kunnen komen
              {
                  throw new ArgumentException("At least one value has the samen Id and parentId [{0}]".FormatInvariant(itemWithIdAndParentIdIsTheSame));
              }
   
              var nodes = valuesCache.Select(v => new Node<T>(v));
              return CreateTree(nodes, idSelector, parentIdSelector);
               
          }
   
          public static IEnumerable<Node<T>> CreateTree<TId>(IEnumerable<Node<T>> rootNodes, Func<T, TId> idSelector, Func<T, TId?> parentIdSelector)
              where TId : struct
   
          {
              var rootNodesCache = rootNodes.ToList();
              var duplicates = rootNodesCache.Duplicates(n => n).ToList();
              if (duplicates.Any())
              {
                  throw new ArgumentException("One or more values contains {0} duplicate keys. The first duplicate is: [{1}]".FormatInvariant(duplicates.Count, duplicates[0]));
              }
               
              foreach (var rootNode in rootNodesCache)
              {
                  var parentId = parentIdSelector(rootNode.Value);
                  var parent = rootNodesCache.FirstOrDefault(n => IsSameId(idSelector(n.Value), parentId));  
                   
                  if (parent != null)
                  {
                      parent.Add(rootNode);
                  }
                  else if (parentId != null)
                  {
                      throw new ArgumentException("A value has the parent ID [{0}] but no other nodes has this ID".FormatInvariant(parentId.Value));
                  }
              }
              var result = rootNodesCache.Where(n => n.IsRoot);
              return result;
          }
   
   
          private static bool IsSameId<TId>(TId id, TId? parentId)
              where TId : struct
          {
              return parentId != null && id.Equals(parentId.Value);
          }
   
          #region Equals en ==
   
          public static bool operator ==(Node<T> value1, Node<T> value2)
          {
              if ((object)(value1) == null && (object) value2 == null)
              {
                  return true;
              }
              return ReferenceEquals(value1, value2);
          }
   
          public static bool operator !=(Node<T> value1, Node<T> value2)
          {
              return !(value1 == value2);
          }
   
          public override bool Equals(Object anderePeriode)
          {
              var valueThisType = anderePeriode as Node<T>;
              return this == valueThisType;
          }
   
          public bool Equals(Node<T> value)
          {
              return this == value;
          }
   
          public bool Equals(Node<T> value1, Node<T> value2)
          {
              return value1 == value2;
          }
   
          bool IEqualityComparer.Equals(object value1, object value2)
          {
              var valueThisType1 = value1 as Node<T>;
              var valueThisType2 = value2 as Node<T>;
   
              return Equals(valueThisType1, valueThisType2);
          }
   
          public int GetHashCode(object obj)
          {
              return GetHashCode(obj as Node<T>);
          }
   
          public override int GetHashCode()
          {
              return GetHashCode(this);
          }
   
          public int GetHashCode(Node<T> value)
          {
              return base.GetHashCode();
          }
          #endregion
        }
      }
      

This the code of the extensions that where used:


      using System;
      using System.Collections.Generic;
      using System.Globalization;
      using System.Linq;
      using System.Text;

      namespace BlogConsole.Nodes
      {
        public static class NodeExtensions
        {
            public static IEnumerable<T> Values<T>(this IEnumerable<Node<T>> nodes)
            {
                return nodes.Select(n => n.Value);
            }
        }

        public static class OtherExtensions
        {
            public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
            {
                var grouped = source.GroupBy(selector);
                var moreThen1 = grouped.Where(i => i.IsMultiple());

                return moreThen1.SelectMany(i => i);
            }

            public static bool IsMultiple<T>(this IEnumerable<T> source)
            {
                var enumerator = source.GetEnumerator();
                return enumerator.MoveNext() && enumerator.MoveNext();
            }

            public static IEnumerable<T> ToIEnumarable<T>(this T item)
            {
                yield return item;
            }

            public static string FormatInvariant(this string text, params object[] parameters)
            {
                // This is not the "real" implementation, but that would go out of Scope
                return string.Format(CultureInfo.InvariantCulture, text, parameters);
            }
        }
      }
      

Leave a Comment

Comment

Comments

C# CSharp Blog Comment

Eliton A. 14-11-2013 / Reply

Thank you Alex! Really good post, everything works. I'm new to C# and .Net MVC, I also have been looking all over to  have a well structured Node class that can be easily implemented and extended. Then I found the link to your blog on Stack Overflow!!! This saves me hours of work. Thanks for share.<


C# CSharp Blog Comment

Donny V 21-11-2013 / Reply

Great tree structure class! I added a "Leaves" function to return nodes that have no children.
Maybe this might come in handy for someone.

public IEnumerable<Node<T>> Leaves
        {
            get
            {
                return LeafSearch(this);
            }
        }

        private List<Node<T>> LeafSearch(Node<T> node)
        {
            var leaves = new List<Node<T>>();

            foreach (var child in node.Children)
            {
                if (child.Children.Count() == 0)
                {
                    leaves.Add(child);
                }
                else
                {
                    leaves.AddRange(LeafSearch(child));
                }
            }

            return leaves;
        }


C# CSharp Blog Comment Reply

Alex Siepman 26-12-2013 / Reply

Hi Donny,

Thanks for your compliment. If I would create a Leaves property, I would use a more functional way of programming, like most of the other Node properties. For example:

        public IEnumerable<Node<T>> Leaves
        {
            get
            {
                return Descendants.Where(n => !n.Children.Any());
            }
        }

At least that is a bit shorter and the meaning is easier to read.


C# CSharp Blog Comment

Rohan 09-07-2020 / Reply

Hey Alex, Great Blog, How can I achieve sorting of child is if I already have the order for perticular parent node?


C# CSharp Blog Comment

Dmitri Panfilov 02-10-2020 / Reply

You forgot to add spec. for CultureInfo.InvariantCulture ... Can you add it?


C# CSharp Blog Comment

Max Bayne 20-04-2021 / Reply

thanks alex i have question how to loop the tree from down to up like (Traversals) ?


C# CSharp Blog Comment Reply

Alex Siepman 20-04-2021 / Reply

That is simple. Just use .Ancestors property as shown in the examples in my blog.


C# CSharp Blog Comment Reply

Max Bayne 21-04-2021 / Reply

thanks alex , but not working with me its fetch the Parent Only like below This is my Tree : Root Node1 > ChildA Node2 > ChildA > ChildB When i use This code var lastAccount =treeOfAccountBalances[0].Descendants.Last(c => c.IsRoot == false); foreach (var node in lastAccount.Ancestors) { Debug.WriteLine(node.Value.AccountDescription); } when i try to fetch from last node called (ChildB) it give me this result Node2 Root i need this ChildB ChildA Node2 ChildA Node1 Root its somthing called (traverse Tree)


C# CSharp Blog Comment Reply

Max Bayne 21-04-2021 / Reply

Thanks Alex i got solution by this code treeOfAccountBalances[0].All.Reverse(); you can update your code and put Method Called (Traverse) and use this Extensions public static IEnumerable> Traverse(this IList> tree) where TNode:class { return tree[0].All.Reverse(); }


C# CSharp Blog Comment Reply

Alex Siepman 21-04-2021 / Reply

If you go to the root, better use the Root property. So myNode.Root.All...


C# CSharp Blog Comment

Slawa 11-02-2022 / Reply

Thank you so much, exactly what I was looking for! A simple and convenient class for representing a tree. Especially liked the method CreateTree (), which forms a hierarchy from a flat list!


C# CSharp Blog Comment

Winform Treeview and binding 25-03-2022 / Reply

Great example. Using your class, I could I bind my data to a treeview winform?


C# CSharp Blog Comment Reply

csharpdev 05-12-2022 / Reply

I'm also curious on whether we can use this class to manipulate winforms treeview and treenode classes


C# CSharp Blog Comment

Josh Aguilar 22-10-2022 / Reply

Super useful class my friend. Thank You.