Recursive Select in C# and LINQ

To complement my post on recursive joins to create hierarchical structures, here's the opposite code to flatten a hierarchy by performing a recursive select in LINQ and returning a flat IEnumerable<T>.

Flattening Hierarchical Data

Consider the following hierarchical data:

NodeData[] nodes = new NodeData[]
{
   new NodeData
   {
      Text = "A",
      Children = new NodeData[]
      {
         new NodeData { Text = "C" },
         new NodeData { Text = "D" },
      }
   },
   new NodeData
   {
      Text = "B",
      Children = new NodeData[]
      {
         new NodeData
         {
            Text = "E",
            Children = new NodeData[]
            {
               new NodeData { Text = "F" },
            }
         }
      }
   }
};

With the arbitrary class (you don't have to use this class, you can select against any class):

public class NodeData
{
   public string Text { get; set; }

   public NodeData[] Children { get; set; }
}

The above data can be flattened using one of my RecursiveSelect IEnumerable<T> extension methods:

foreach (NodeData node in nodes.RecursiveSelect(node => node.Children))
{
   Console.WriteLine(node.Text);
}

The key to this working is the child selector which is recursively called to gather a flattened structure over the node data.

The Recursive Select LINQ Extension Methods

Again, I chose to call the LINQ extension methods RecursiveSelect rather than HierarchicalSelect as I think "recursive" is easier to type and spell than "hierarchical" or "hierarchy".

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

public static class RecursiveSelectExtensions
{
   public static IEnumerable<TSource> RecursiveSelect<TSource>(this IEnumerable<TSource> source,
   Func<TSource, IEnumerable<TSource>> childSelector)
   {
      return RecursiveSelect(source, childSelector, element => element);
   }

   public static IEnumerable<TResult> RecursiveSelect<TSource, TResult>(this IEnumerable<TSource> source,
      Func<TSource, IEnumerable<TSource>> childSelector, Func<TSource, TResult> selector)
   {
      return RecursiveSelect(source, childSelector, (element, index, depth) => selector(element));
   }

   public static IEnumerable<TResult> RecursiveSelect<TSource, TResult>(this IEnumerable<TSource> source,
      Func<TSource, IEnumerable<TSource>> childSelector, Func<TSource, int, TResult> selector)
   {
      return RecursiveSelect(source, childSelector, (element, index, depth) => selector(element, index));
   }

   public static IEnumerable<TResult> RecursiveSelect<TSource, TResult>(this IEnumerable<TSource> source,
      Func<TSource, IEnumerable<TSource>> childSelector, Func<TSource, int, int, TResult> selector)
   {
      return RecursiveSelect(source, childSelector, selector, 0);
   }

   private static IEnumerable<TResult> RecursiveSelect<TSource, TResult>(this IEnumerable<TSource> source,
      Func<TSource, IEnumerable<TSource>> childSelector, Func<TSource, int, int, TResult> selector, int depth)
   {
      return source.SelectMany((element, index) => Enumerable.Repeat(selector(element, index, depth), 1)
         .Concat(RecursiveSelect(childSelector(element) ?? Enumerable.Empty<TSource>(),
            childSelector, selector, depth + 1)));
   }
}