Local Functions in C# 7

No comments

In my last post, I briefly mentioned the new C# feature known as local functions. I’ll write a bit about them here to get the word out a bit more and give me a reason to spend more time with them.

Let’s just dive right in. We’ll have this function that allows us to roll some virtual dice and elect to keep only those dice with the highest results or lowest results. It will then return the sum of those results. I’m passing in randomNumberGenerator partly for testing purposes but also because I might want to keep a single instance of Rand or use a true random number generator service or otherwise swap out the implementation1.

public int RollDice(int numberOfDice, int diceSides, DiceToKeep diceToKeep, int numberOfDiceToKeep, IRandomNumberGenerator randomNumberGenerator)
{
    var rolls = new List(numberOfDice);
    for (var i = 0; i < numberOfDice; ++i)
    {
        rolls.Add(randomNumberGenerator.GetRandomNumber(1, diceSides));
    }

    // call the local function to reduce the number of dice
    // based on the diceToKeep and numberOfDiceToKeep
    // parameters.
    var finalDice = limitDiceToKeep().ToList();
    return finalDice.Sum();

    // here is the local function, notice it is inside the method.
    IEnumerable limitDiceToKeep()
    {
        switch (diceToKeep)
        {
            case DiceToKeep.Greatest:
                return rolls.OrderByDescending(x => x).Take(numberOfDiceToKeep);
            case DiceToKeep.Least:
                return rolls.OrderBy(x => x).Take(numberOfDiceToKeep);
            default:
                return rolls;
        }
    }
}

It’s all very straight forward. Create a method like you normally would, just put it inside the method code and don’t add modifiers like public or private. What is important to note is that all of the variables and parameters of the parent method are available to the local function. Where the local function is placed in the flow of code doesn’t matter other than variables it is using from the outer method must be declared first. The code below is functionally equivalent to the code above (at least my unit tests tell me so!).

public int RollDice(int numberOfDice, int diceSides, DiceToKeep diceToKeep, int numberOfDiceToKeep, IRandomNumberGenerator randomNumberGenerator)
{
    var rolls = new List(numberOfDice);
    // here is the local function, notice it is moved higher in the method.
    IEnumerable limitDiceToKeep()
    {
        switch (diceToKeep)
        {
            case DiceToKeep.Greatest:
                return rolls.OrderByDescending(x => x).Take(numberOfDiceToKeep);
            case DiceToKeep.Least:
                return rolls.OrderBy(x => x).Take(numberOfDiceToKeep);
            default:
                return rolls;
        }
    }
    
    for (var i = 0; i < numberOfDice; ++i)
    {
        rolls.Add(randomNumberGenerator.GetRandomNumber(1, diceSides));
    }
  
    var finalDice = limitDiceToKeep().ToList();
    return finalDice.Sum();

}

As I suspected, the compiler is quite happy to nest these functions too. Pushing the limits of silliness, we can pull the .Take(numberOfDiceToKeep) into its own local function inside the local function limitDiceToKeep.

public int RollDice(int numberOfDice, int diceSides, DiceToKeep diceToKeep, int numberOfDiceToKeep, IRandomNumberGenerator randomNumberGenerator)
{
    var rolls = new List(numberOfDice);

    IEnumerable limitDiceToKeep()
    {
        switch (diceToKeep)
        {
            case DiceToKeep.Greatest:
                return takeRolls(rolls.OrderByDescending(x => x));
            case DiceToKeep.Least:
                return takeRolls(rolls.OrderBy(x => x));
            default:
                return rolls;
        }

        // a nested local function
        IEnumerable takeRolls(IEnumerable sortedRolls)
        {
            return sortedRolls.Take(numberOfDiceToKeep);
        }
    }
    
    for (var i = 0; i < numberOfDice; ++i)
    {
        rolls.Add(randomNumberGenerator.GetRandomNumber(1, diceSides));
    }

    // call the local function to reduce the number of dice
    // based on the diceToKeep and numberOfDiceToKeep
    // parameters.
    var finalDice = limitDiceToKeep().ToList();
    return finalDice.Sum();
}

The compiler may be happy with that, but I’m not! This is hopefully illustrating my main concern with locals. Namely, that instead of cleaning up your code, you can quickly make it more messy if you overuse it. You probably also see a benefit to locals here. If that Take call were more complicated, I wouldn’t want to repeat it in two places. Instead of creating a separate private method for my Take logic, I have communicated my intention that it’s only relevant to the RollDice method. That’s one of the main benefits I see to local functions beyond those cited in the MSDN article.

In the MSDN article, a couple of specific cases are presented that used the local function to wrap some quirks of iterators and asyncs with respect to error handling. I would have probably gone a little further and put the validations they show in their own local so that the validations don’t overwhelm the intention of the code. I’ll have to play more with it, but this feels a little better to me:

public Task PerformLongRunningWork(string address, int index, string name)
{
    validateParameters();
    return longRunningWorkImplementation();

    async Task longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }

    void validateParameters(){
        if (string.IsNullOrWhiteSpace(address))
            throw new ArgumentException(message: "An address is required", paramName: nameof(address));
        if (index < 0)
            throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));
    }
}

I like this more because I consider validation of parameters to normally be noise in the face of my business logic2. Parameter validation is neccessary and good, but it can easily obscure the purpose of the method.

Overall, I like the new option. It doesn’t give me the ability to do anything I couldn’t do before, but it does let me be a little more clear on intention and keeps me from having single-use private methods that sometimes can end up getting moved around in a class until you lose track of which public methods they were supporting. I supsect this feature will be used poorly by some people to make some really messy code, but that shouldn’t hold the language back.


  1. Random number generation is a bit of a complicated topic in .Net, Java, and many other programming languages. For an example of a Rand helper in .Net, see John Skeet’s article here.
  2. If I have complex validation rules that become their own business logic, I’m probably better off breaking them out into their own testable units. One possible approach is using the Specification Pattern where you probably only need and and maybe andnot.