These extension methods do exactly what you want:
public static partial class LinqExtensions
{
public class Node<T>
{
internal Node() { }
public int Level { get; internal set; }
public Node<T> Parent { get; internal set; }
public T Item { get; internal set; }
public IList<Node<T>> Children { get; internal set; }
}
public static IEnumerable<Node<T>> ByHierarchy<T>(
this IEnumerable<T> source,
Func<T, bool> startWith,
Func<T, T, bool> connectBy)
{
return source.ByHierarchy<T>(startWith, connectBy, null);
}
private static IEnumerable<Node<T>> ByHierarchy<T>(
this IEnumerable<T> source,
Func<T, bool> startWith,
Func<T, T, bool> connectBy,
Node<T> parent)
{
int level = (parent == null ? 0 : parent.Level + 1);
if (source == null)
throw new ArgumentNullException("source");
if (startWith == null)
throw new ArgumentNullException("startWith");
if (connectBy == null)
throw new ArgumentNullException("connectBy");
foreach (T value in from item in source
where startWith(item)
select item)
{
var children = new List<Node<T>>();
Node<T> newNode = new Node<T>
{
Level = level,
Parent = parent,
Item = value,
Children = children.AsReadOnly()
};
foreach (Node<T> subNode in source.ByHierarchy<T>(possibleSub => connectBy(value, possibleSub),
connectBy, newNode))
{
children.Add(subNode);
}
yield return newNode;
}
}
public static void DumpHierarchy<T>(this IEnumerable<Node<T>> nodes, Func<T, string> display)
{
DumpHierarchy<T>(nodes, display, 0);
}
private static void DumpHierarchy<T>(IEnumerable<LinqExtensions.Node<T>> nodes, Func<T, string> display, int level)
{
foreach (var node in nodes)
{
for (int i = 0; i < level; i++) Console.Write(" ");
Console.WriteLine (display(node.Item));
if (node.Children != null)
DumpHierarchy(node.Children, display, level + 1);
}
}
}
You can use them as follows:
categories.ByHierarchy(
cat => cat.ParentId == null, // assuming ParentId is Nullable<int>
(parent, child) => parent.Id == child.ParentId)
.DumpHierarchy(cat => cat.Name);