Hm, to be honest, in your implementation I don’t like the fact that the private Dump() is entered while the StringBuilder is in the middle of a line. You did comment on the “precondition”, but the code would be more self-documenting if that precondition weren’t necessary. My solution generates complete lines at a time, and also it doesn’t create an instance of Dumper so I made it a static class.

After some time thinking I've come with non-recursive solution with only one function Dump (but with one extra class-container). Interesting that it have some common with recursive (last, parent indent)

Also I've benchmarked this solution and it is at least not slower (on toll/deep trees) and sometimes 2x faster (on wide trees) 🙂

I would like to ask whether this implementation could benefit from hoisting the 'last' variable out of the loop?

Similarly, the only use of the "child" local variable is in the recursive call, so I wonder if there is a significant reason NOT to inline it?

I always seem to learn something new from your posts Eric. I didn't know that arrays implemented IList<T>. I found it quite interesting that the mutable methods of IList<T> throw NotSupportedException and rely on explicit interface implementation to discourage their use. This is both clever but annoying because it violates Liskov. Is this something to be emulated or avoided?

This is *very* close to a solution I came up with. I also refactored using LINQ to see if I could further condense the result (e.g., I split the children into a head-list and last-node using Take(root.Children.Count – 1) and Last()). When you write LINQ solutions do they always start out as LINQ queries or do you sometimes refactor to LINQ expressions?

Regarding the non-static nature of Eric's solution: we often use a similar paradigm of creating instances inside static methods to do the real work. This is one way to make the public static method "thread safe". Another alternative is to pass all of the state on the stack to private static methods, but this can lead to less readable method signatures. For example, the static entry point could have created a StringBuilder object and passed it to static recursion methods. But if additional state was required in the future, you would end up changing method signatures.