Refactoring Code to Use C# Local Functions

In a previous post I talked about the potential use of local functions to replace comments. This generated some good discussion on Twitter and in the comments.

In this post I wanted to show another use of local functions to potentially improve readability.

Improving Iteration Code Readability

Consider the following simple console app:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var names = new[]{ "Sarah Smith", "Gentry Jones", "Arnold Appleview" };

            // Output names with surname first
            foreach (var name in names)
            {
                var nameParts = name.Split(" ");
                var firstName = nameParts[0];
                var lastName = nameParts[1];
                var formattedName = $"{lastName}, {firstName}";
                Console.WriteLine(formattedName);
            }

            Console.ReadLine();
        }
    }
}

The code inside the for loop could be considered at a lower level of abstraction/more detailed than the rest of the code and the Main method itself.

We could take the contents of the for loop and refactor it into a private function in the class as follows:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var names = new[] { "Sarah Smith", "Gentry Jones", "Arnold Appleview" };
            
            foreach (var name in names)
            {
                OutputWithSurnameFirst(name);
            }

            Console.ReadLine();
        }

        private static void OutputWithSurnameFirst(string name)
        {
            var nameParts = name.Split(" ");
            var firstName = nameParts[0];
            var lastName = nameParts[1];
            var formattedName = $"{lastName}, {firstName}";
            Console.WriteLine(formattedName);
        }
    }
}

Notice the Main method is not mixing as many levels of abstraction/detail now – we have also been able to remove the comment because the method name OutputWithSurnameFirst describes what the comment used to. If we are reading the Main method, we don’t have to burden our concentration with the details of the how the names are output unless we want to.

This approach is fine, but it could be argued that we have “polluted” the class with a method that is only used once in the Main method. It could also be argued that declaration of the OutputWithSurnameFirst method is not as close to it’s use as a local method would be.

Let’s take a look next at a version of the code that instead uses a local method:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var names = new[] { "Sarah Smith", "Gentry Jones", "Arnold Appleview" };

            foreach (var name in names)
            {
                OutputWithSurnameFirst(name);
            }

            Console.ReadLine();

            static void OutputWithSurnameFirst(string name)
            {
                var nameParts = name.Split(" ");
                var firstName = nameParts[0];
                var lastName = nameParts[1];
                var formattedName = $"{lastName}, {firstName}";
                Console.WriteLine(formattedName);
            }
        }
    }
}

In the preceding example, the local method has taken the place of the class-level method, it has however made the overall length of the Main method longer. In this example it could be argued that the previous version is more readable.

Let’s take a look at another example next.

Improving C# Lambda Code Readability with Local Functions

In some cases, a local function may improve the readability of lambda function code.

Take the following initial code:

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

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var strings = new[] { "Hello", "31414", "2HI9" };

            foreach (var name in strings)
            {
                IEnumerable<bool> areUpperLetters = name.Select(x => char.IsLetter(x) && char.IsUpper(x));
                Console.WriteLine($"{name} upper digits = {string.Join(",", areUpperLetters)}");
            }

            Console.ReadLine();
        }
    }
}

In the preceding code, the variable named areUpperLetters gives us a clue as to what the lambda does, which is good, often a well-named variable can really improve readability. This code could be refactored to a local functions as follows:

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

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var strings = new[] { "Hello", "31414", "2HI9" };

            foreach (var name in strings)
            {
                IEnumerable<bool> areUpperLetters = name.Select(IsUpperCaseLetter);
                Console.WriteLine($"{name} upper digits = {string.Join(",", areUpperLetters)}");
            }

            Console.ReadLine();

            static bool IsUpperCaseLetter(char c)
            {
                return char.IsLetter(c) && char.IsUpper(c);
            }
        }
    }
}

Now the logic that was contained in the lambda has been moved to the local function called IsUpperCaseLetter.

To see local functions in action and also learn a whole heap of C# tips check out my C# Tips and Traps Pluralsight course. You can also start watching the course with a free trial

SHARE:

Comments (2) -

  • Kerry Patrick

    1/22/2020 2:42:11 PM | Reply

    As a counter-argument to the argument that adding a private method pollutes the class, I would argue that the local function pollutes the method. If I'm reading a public class method to understand what it does, I'm looking for a high level of abstraction. I may only care that the method is building a name somehow and  outputting it to the console. I don't care about the details of how the name is constructed, I just want to know that it's constructed. Putting a local function inside this method forces me to read through those details to get to the end of the method. And while it may be a helpful convention to place all local functions at the bottom of the method, it's not required. Therefore,  I have to read past all the local functions to see if the developer included any functionality beyond that local function. I've also seen this feature abused in my team's codebase, where multiple local functions are nested in a single method, making the method even harder to decipher.

    Thinking about it from a refactoring perspective, too, if I'm driving out a class definition using TDD, I'm going to end up writing procedural methods that may go on for a few lines, since that's the simplest thing that satisfies the test. During the refactor step of Red-Green-Refactor, the first natural thing I'm going to do is clean up that method by extracting out other private methods which have distinct responsibilities. The SECOND natural thing I'm going to do is, if I see the class growing too large I'm going to use an Extract Class refactor to start extracting distinct responsibilities out into their own classes. How much more difficult is it going to be to extract local functions out into a separate class than private methods? Resharper has support for the latter. Do they or will they ever be able to support the former, given the possibility of closures? Local functions make it easier to create classes that are too large and hide too many responsibilities.

    • Jason Roberts

      1/23/2020 12:43:38 AM | Reply

      Hi Kerry, I can't disagree with the points you're making here, especially multiple nested local functions sounds particularly yuk. I'm really enjoying these discussions around local functions that these articles are generating. Do you think local functions have a place at all in your codebases?

Pingbacks and trackbacks (1)+

Add comment

Loading