---
title: "local functions vs lambda expressions"
description: ".NET knows **local functions** and lambda expressions. You can almost take them interchangeably, but there are also some differences between them.\n\nThis article will show the differences between them."
updated: 2022-10-30
tags: [".NET", "Performance", "Delegate", "C#"]
canonical: "https://steven-giesel.com/blogPost/8e2f551d-0812-4c5f-9f00-b353a37c48a1/local-functions-vs-lambda-expressions"
---

# local functions vs lambda expressions

.NET knows **local functions** and lambda expressions. You can almost take them interchangeably, but there are also some differences between them.

This article will show the differences between them.

## What are local functions?

A local function is a method, which is nested in another member. A typical example would be like that:
```csharp
int Fib(int n) 
{
    if (n < 0)
        throw new ArgumentException("n must be >= 0", nameof(n));

    return FibLocal(n);

    int FibLocal(int number) 
    {
        if (number == 0)
            return 0;
        if (number == 1)
            return 1;
        
        return FibLocal(number - 1) + FibLocal(number - 2);
    }
}
```

Use-cases are like in the example above to have a sanitation check. Also only `Fib` can call `FibLocal`. So there is no chance that another outside function can mistakenly call `FibLocal`. **Local functions** can make the intend of your function clearer by separating some concerns. There are also circumstances, where it makes your code easier.

:bulb: Local functions are like "regular" private methods on your class. The compiler generates them for you. You can check [sharplab.io](https://sharplab.io/#v2:CYLg1APgAgTAjAWAFDIJYDsAuACAYqgIwAoMd0BKbZAb2W3u1QDNsj1sAebABnLoYGYAFgCcA9gHds6AKZSAgiIDmAVwC2MrAFEAHgGMZAB0yox6IgCJ2alQGccBGdgB8AXh4WANNICGGsUxs5OQA3Mj89FAA7HiEADJiej4ANkFhKEgCpLEECUmp2ejqjiKUEdi0mQIMzKxFaiXYru685dUM0Tzp7fS1bMUyIk3ucHxVPR0xcN09bZM5eSn9DYPYALTYo9hgC4lL9Y0bMKHlAL7Ip0A) with the given example from above to see the "magic". So everything written below applies to "local" functions as well as "regular" functions.

---

On first glance they look exactly the same as [lambda functions](https://steven-giesel.com/blogPost/aa77f7fb-41a7-47d2-8e48-ef9e101cf08e) but there are some noticeable differences between them, so let's go through to give you a better overview.

## Recursion
The example above (Fibonacci) is done recursively via a local function. You can't do this easily with lambda expression, so this following code, would not compile:
```csharp
Func<int, int> FibLambda = n =>
{
    if (n == 0 || n == 1)
        return n;

    return FibLambda(n - 2) + FibLambda(n - 1);
};
```

You get an error: "*Local variable 'FibLambda' might not be initialized before accessing*". You have to write something like this to make it work (basically initializing the lambda with null):
```csharp
Func<int, int> FibLambda = null;
FibLambda = n =>
{
    if (n == 0 || n == 1)
        return n;

    return FibLambda(n - 2) + FibLambda(n - 1);
};
```

And that code brings you some compiler warning, especially when you enable nullable reference types. So not optimal in my opinion.

## `yield` aka generator functions
Local functions in contrast to delegates in general (also including lambdas) can use the[ `yield`](https://steven-giesel.com/blogPost/994467f6-2429-4534-ad43-c0777076ab22) keyword. 
```csharp
IEnumerable<int> GetNumbers(int i)
{
    return GetNumbersLocal(i);

    static IEnumerable<int> GetNumbersLocal(int end)
    {
        for (var i = 0; i < end; i++)
            yield return i;
    }
}
```

If you try the same with a lambda expression, you are greeted with the following error: *"Only methods, operators and accessors could contain 'yield' statement"*.

## Performance
In short: A **local function** is just that, a function. A lambda on the contrary is a delegate, which has to be created. Delegates do create new allocations and can have other implications. I will put some links at the end of the post. Anyway **in general** local functions outperform lambdas. There are certain scenarios where a local function gets "converted" to a delegate thus having the same performance.

Also, local functions are more efficient with capturing local variables: lambdas usually capture variables into a class, while local functions can use a struct (passed using ref), which again avoids an allocation.

Here a small benchmark, where we compare local functions against lambdas expression. One time a `static` version, which doesn't capture any state from the outside world (aka closure) and one time with.

```csharp
[MemoryDiagnoser()]
public class Benchmark
{
    private const int Iterations = 100;

    [Params(4)]
    public int Number { get; set; }

    [Benchmark]
    public int LocalFunctionNonStatic()
    {
        var sum = 0;
        for (var i = 0; i < Iterations; i++)
            sum += Add(i);

        return sum;

        int Add(int number) => number + Number;
    }

    [Benchmark]
    public int LocalFunctionStatic()
    {
        var sum = 0;
        for (var i = 0; i < Iterations; i++)
            sum += Add(i);

        return sum;

        static int Add(int number) => number + 4;
    }

    [Benchmark]
    public int LambdaCreatedNonStatic()
    {
        Func<int, int> add = n => n + Number;
        var sum = 0;
        for (var i = 0; i < Iterations; i++)
            sum += add(i);

        return sum;
    }

    [Benchmark]
    public int LambdaCreatedStatic()
    {
        Func<int, int> add = static n => n + 4;
        var sum = 0;
        for (var i = 0; i < Iterations; i++)
            sum += add(i);

        return sum;
    }
}
```

Results:
```csharp
// * Summary *

BenchmarkDotNet=v0.13.1.1833-nightly, OS=macOS Monterey 12.6 (21G115) [Darwin 21.6.0]
Apple M1 Pro, 1 CPU, 10 logical and 10 physical cores
.NET SDK=7.0.100-rc.2.22477.23
  [Host]     : .NET 7.0.0 (7.0.22.47203), Arm64 RyuJIT AdvSIMD
  DefaultJob : .NET 7.0.0 (7.0.22.47203), Arm64 RyuJIT AdvSIMD


|                 Method | Number |      Mean |    Error |   StdDev |  Gen 0 | Allocated |
|----------------------- |------- |----------:|---------:|---------:|-------:|----------:|
| LocalFunctionNonStatic |      4 |  48.75 ns | 0.076 ns | 0.064 ns |      - |         - |
|    LocalFunctionStatic |      4 |  47.22 ns | 0.115 ns | 0.108 ns |      - |         - |
| LambdaCreatedNonStatic |      4 | 136.90 ns | 0.288 ns | 0.240 ns | 0.0100 |      64 B |
|    LambdaCreatedStatic |      4 | 130.43 ns | 0.396 ns | 0.351 ns |      - |         - |
```

We can see all the effects as described above. The lambda can cause allocations (with closures) whereas the local function doesn't. Also the runtime is faster.

:bulb: A small tip: You can decorate a local function (as a regular function) with the static keyword to prohibit this closure behavior. The same applies to lambda expressions:
```csharp
void MyMethod()
{
    int someState = 2;

    // Accessing someState inside the lambda would lead to a compiler error
    Func<int> myFunc = static () => { return 2; }

    static void MyMethodLocal()
    {
        // Accessing someState would lead to a compiler error
    }
}
```

## Other things
Here are also other things, which could make a difference for you:
 * Local functions can be generic. And I don't mean that they "inherit" a generic type from the parent, but they can define a new generic constraint if they want to. A lambda expression only can use the generic constraints from the outer scope instead of creating one on its own).
 * Local functions can be defined after the return statement. Obviously, that doesn't work for lambdas, as they are "regular" variables, which have to be declared before use.
 * Reflection: A local function is really a "regular" function, which can be looked up via reflection. Warning: Don't do this! As said earlier, these are auto-generated functions whereby the name can change. So you are looking for trouble if you do this. Anyway if you would list all private functions of a class, local functions would appear.
 * Lambdas can not have the `unsafe` keyword, local functions can have that.
 * **LINQ**: When it comes to **LINQ** queries, there is virtually no difference as LINQ always captures either an `Expression` or a `Func`, which again are delegates. So also your local function will be treated as a delegate in this scenario. You don't gain the performance improvements as described above.

## Closing
Lambdas are a convenient and easy way of creating a small function. They have some "short-comings" which might or might not be relevant in your use case. In any way it is interested to see and know those differences. A lot of people do like (including me) to refactor certain small aspects out of a function into local function. At the end it boils also down to personal preference.

## Resources
 * [*Generator-Function in C# - What does yield do?*](https://steven-giesel.com/blogPost/994467f6-2429-4534-ad43-c0777076ab22)
 * [*Delegate, Action, Func, Lambda expression - What the heck!?*](https://steven-giesel.com/blogPost/aa77f7fb-41a7-47d2-8e48-ef9e101cf08e)
 * [*Local functions (C# Programming Guide)*](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions)