Recursion in C#

·

2 min read

It's not often that we use recursion in real-world programming. The last time I wrote recursion was during an interview 6+ years ago. Recursive functions are not performant and thus are avoided. A few days ago, however, I had a task to navigate down a tree given an ancestor node, so ... recursion it is!

The Scenario

Given a GUID Id of an ancestor Account object (think like a Salesforce account or an Organization), find all the Accounts under that ancestor to a maximum depth of 5. Something like this:

Given Account A's Id, get all the Accounts under it. First, here's the function which executes the recursion:

public async Task<IEnumerable<Account>> GetAccountsWithChildren(Guid id)
    {
        // Get all accounts first
        var allAccounts = await GetAccounts();
        // Bail early if no results
        if (allAccounts == null || allAccounts.Count == 0) return new List<Account>();
        // Get the root account
        var rootAccount = allAccounts.FirstOrDefault(x => x.Id == id);
        // Bail if root account not found
        if (rootAccount == null) return new List<Account>();
        // Define the max depth we want to run our recursion
        var maxDepth = 5;
        // Set up the list we're going to populate with results
        List<Account> childAccounts = new List<Account>();
        // Add the root account at the start
        childAccounts.Add(rootAccount);
        // Run the recursion!
        ExtractAccountsRecursively(maxDepth, id, allAccounts, childAccounts, 0);
        // Finally, return the results
        return childAccounts;
    }

Nothing exceptional in the above method. The fun stuff is in this next bit:

private void ExtractAccountsRecursively(
    int maxDepth, 
    Guid? parentId, 
    IEnumerable<Account> allAccounts, 
    List<Account> childAccounts, 
    int currDepth = 0)
    {
        // Bail if depth reached
        if (currDepth >= maxDepth) return;
        // Bail if parentId is null
        if (!parentId.HasValue) return;
        // Bail if there are no more children
        var currentChildren = allAccounts.Where(a => a.Parent != null && a.Parent.Id == parentId);
        // Increase the current depth to pass down to the recursive function
        currDepth++;
        // Iterate over the children found with the parentId above
        foreach (var account in currentChildren)
        {
            // Prevent duplicates
            if (childAccounts.Contains(account)) continue;
            // Add the account to the child accounts list
            childAccounts.Add(account);
            // Run this function again for the account we're iterating through
            ExtractAccountsRecursively(maxDepth, account.Id, allAccounts, childOnlyAccounts, currDepth);
        }
    }

I commented the code above so it should be easy to follow. The incoming list, allAccounts , is a flat list of Account objects with ParentId property referring to the Id of another Account object.

I suspect there's a better way to write this using yield but that syntax confuses me. If anyone has a good example for the above scenario using yield I'd be interested to check it out.

Cheers!

Did you find this article valuable?

Support Paul K by becoming a sponsor. Any amount is appreciated!