C# 7.0 – Local functions


Facebooktwittergoogle_pluslinkedinmail

There are situations when one needs a small helper method, but it’s not quite obvious where to put it. The method might not be generic enough to expose it as a new entity or even as a class member.

Note: You can view all code examples on GitHub. My environment: Visual Studio 2017 and .NET Framework 4.6.1.

With C# 7.0 we can embed a method inside another method. This way it will be available only within the scope of the parent method.

private void Example1()
{
    Print("Starting long running operation");
    // Do some work
    Print("Operation finished");

    void Print(string message)
    {
        var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        Console.WriteLine($"[{timestamp}] >>> {message}");
    }
}

The parent method arguments and local variables are available for the local function.

private void Example2(string name, int value)
{
    PrintName();

    int newValue = value + 100;
    PrintValues();

    void PrintName()
    {
        Console.WriteLine("Name: " + name);
    }

    void PrintValues()
    {
        Console.WriteLine("Old value: " + value);
        Console.WriteLine("New value: " + newValue);
    }
}

All local functions have access to all other local functions in the same scope.

private void Example3()
{
    PrintAll();

    void PrintAll()
    {
        PrintHeader();
        PrintLine("Line 1");
        PrintLine("One more line");
    }

    void PrintHeader() => Console.WriteLine("Header line");

    void PrintLine(string line) => Console.WriteLine("Line: " + line);
}

As shown in the above example local functions can also be expression-bodied.

Local functions are very useful for iterators where one would like to validate parameters ahead of the first call to get the IEnumerable element.

IEnumerable<TOutput> Convert<TInput, TOutput>(IEnumerable<TInput> source, Func<TInput, TOutput> convertFunction)
{
    // Evaluate arguments
    if (source == null)
    {
        throw new ArgumentNullException(nameof(source));
    }
    if (convertFunction == null)
    {
        throw new ArgumentNullException(nameof(convertFunction));
    }

    // Call iterator method
    return ConvertIterator();

    IEnumerable<TOutput> ConvertIterator()
    {
        foreach (var inputItem in source)
        {
            var outputItem = convertFunction(inputItem);
            yield return outputItem;
        }
    }
}

// The following call will only evaluate arguments
var output = Convert(new[] { 1, 10, 100 }, i => i.ToString());
// The foreach loop will cause ConvertIterator method to be called
foreach (var item in output)
{
    Console.WriteLine("Item: " + item);
}

 

Facebooktwittergoogle_pluslinkedinmail

Leave a comment

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