Local functions compared to Lambda expressions

In some use cases, you could create a lambda expression and use it without needing to create a local function. Here's an example async method that does just that:

public Task<string> PerformLongRunningWorkLambda(string address, int index, string name)
{
    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));

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

    return longRunningWorkImplementation();
}

However, there are a number of reasons to prefer using local functions instead of defining and calling lambda expressions.

First, for lambda expressions, the compiler must create an anonymous class and an instance of that class to store any variables captured by the closure. The closure for this lambda expression contains the address, index and name variables.

Second, lambda expressions are implemented by instantiating a delegate and invoking that delegate. Local functions are implemented as method calls. The instantiation necessary for lambda expressions means extra memory allocations, which may be a performance factor in time critical code paths. Local functions do not incur this overhead.

Third, local functions can be called before they are defined. Lambda expressions must be declared before they are defined. This means local functions are easier to use in recursive algorithms:

public static int LocalFunctionFactorial(int n)
{
    return nthFactorial(n);
    int nthFactorial(int number) => (number < 2) ? 
        1 : number * nthFactorial(number - 1);
}

Contrast that implementation with a version that uses lambda expressions:

public static int LambdaFactorial(int n)
{
    Func<int, int> nthFactorial = default(Func<int, int>);
    nthFactorial = (number) => (number < 2) ? 
        1 : number * nthFactorial(number - 1);
    return nthFactorial(n);
}

Notice that the version using the lambda expression must declare and initialize the lambda expression, nthFactorial before defining it. Not doing so results in a compile time error for referencing nthFactorial before assigning it. Recursive algorithms are easier to create using local functions.

While local functions may seem redundant to lambda expressions, they actually serve different purposes and have different uses. Local functions are more efficient for the case when you want to write a function that is called only from the context of another method.