Local functions in C# 7.0

One of new features of C# 7.0 is support for local functions. Local functions are methods that are defined inside other methods to simplify more complex code and by example to support local recursion. This blog post shows how to use local functions in C# 7.0 and gives some advice in context of technical design of code.

To illustrate local function I will use finding of factorial as an example.


class Program
{
    static void Main(string[] args)
    {
        var i = 6;

        int Factorial(int n)
        {
            if (n == 0)
                return 1;
            else
                return n * Factorial(n - 1);
        }

        Console.WriteLine(Factorial(i));
        Console.ReadLine();
    }
}

Now one may ask why I use recursive call for factorial as it is not so efficient as using for-loop? Let’s move to for-loop.


class Program
{
    static void Main(string[] args)
    {
        var i = 6;

        int Factorial(int n)
        {
            var result = n;

            for (var counter = n - 1; counter >= 1; counter--)
            {
                result = result * counter;
            }

            return result;
        }

        Console.WriteLine(Factorial(i));
        Console.ReadLine();
    }
}

Now we have more effective method for finding factorials. But it is a simple for-loop. Why should we put it local function?

I think it’s good idea in the means of technical design. This for-loop has one concrete meaning – find the factorial – and this is why I prefer to communicate this fact to developer who is working on the code after me.

How to organize the code?

Now let’s see where we should put local functions in our methods. What I suggest here is my personal opinion. Most important part of every method is method body. So, this is coming as first thing. As we name local functions with informative and descriptive names we can keep these functions in the bottom part of method.

To illustrate my idea the modified version of factorial example is here.


class Program
{
    static void Main(string[] args)
    {
        // method body
        if(args.Length == 0)
        {
            Console.WriteLine("Missing argument: input value as integer");
            return;
        }

        var i = int.Parse(args[0]);
      
        Console.WriteLine(Factorial(i));
        Console.ReadLine();

        // local functions
        int Factorial(int n)
        {
            var result = n;

            for (var counter = n - 1; counter >= 1; counter--)
            {
                result = result * counter;
            }

            return result;
        }
    }
}

Now we have code organized and we benefit from this little trick if we use it everywhere in our codebase where local functions are used.

Some considerations of local functions

Some things to keep in mind when considering local functions:

  1. Local functions are part of methods where they are defined and they should not contain any system-wide logic.
  2. Local functions are not visible to other classes and therefore they are tested as unit with methods that own them.
  3. Use local functions only if there is real need for this.
  4. Don’t over-use local functions as you may end up with too long methods that are hard to test.

Wrapping up

Local functions are nice feature in C# 7.0 and there are situations where local functions help us to write cleaner code. Still local functions are not a thing to use to shorten or reorganize the code that doesn’t need them. This can heppen when working with spaghetti code. Consider well if local function is needed before going with this and try it out if it seems like solution. Be aware that local functions are not testable as they are considered to be same local unit with containing method.



See also

5 thoughts on “Local functions in C# 7.0

  • Sébastien Sougnez says:

    Hi,

    what’s the advantage of declaring a local function instead of creating an Action or Func as a variable of your method? I guess the aim is to move C# toward a TypeScripty syntax (that is itself moving toward a C-sharpy syntax), but I don’t really see the added value, except, maybe, the readability of the code. And even this does not hold as now, we have expression body that replace usual method declarations syntax by lambda expression so…

  • Dave R. says:

    Your factorial method shouldn’t be local as it’s something that could obviously be shared elsewhere. You don’t provide a compelling argument for the need for local functions at all. What was the point?

  • wekempf says:

    To address both replies so far:

    Lambdas are more limited than functions. For instance, they cannot do yield return. I wish they’d fix those inconsistencies, but the simpler syntax here is still nice. The yield return problem is actually one of the more compelling use cases for local functions. Generator methods (methods that do yield return) are entirely lazy, which is a problem for doing parameter validation (the validation won’t happen when you call the method, but instead will occur when you MoveNext on the enumerable for the first time). For this reason it’s a common pattern to break such functions in two with a public function that does the validation and then calls a non-public version that does the iteration. This is ugly, though, and you don’t really want any other code to ever call the helper method. Declaring it as a local function solves all of these issues. There’s other use cases as well, but this is the most compelling.

  • wekempf says:

    Oh, I should have also said in the case of lambdas (Action, Func, etc.) you have to allocate a delegate, which has a runtime cost in both performance and memory, even if it’s small. With local functions there’s no cost here.

  • Here is a use case for local functions that I consider more compelling.

    void MyFunction()
    {
    void HandleException()
    {
    //cleanup code here
    }

    try
    {
    //operation that can fail here
    }
    catch (IOException)
    {
    HandleException();
    }
    // many other catch clauses all handled the same way
    catch (NotSupportedException)
    {
    HandleException();
    }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *