228

I am looking at the new implementations in C# 7.0 and I find it interesting that they have implemented local functions but I cannot imagine a scenario where a local function would be preferred over a lambda expression, and what is the difference between the two.

I do understand that lambdas are anonymous functions meanwhile local functions are not, but I can't figure out a real world scenario, where local function has advantages over lambda expressions

Any example would be much appreciated. Thanks.

Sid
  • 14,176
  • 7
  • 40
  • 48

5 Answers5

342

This was explained by Mads Torgersen in C# Design Meeting Notes where local functions were first discussed:

You want a helper function. You are only using it from within a single function, and it likely uses variables and type parameters that are in scope in that containing function. On the other hand, unlike a lambda you don't need it as a first class object, so you don't care to give it a delegate type and allocate an actual delegate object. Also you may want it to be recursive or generic, or to implement it as an iterator.

To expand on it some more, the advantages are:

  1. Performance.

    When creating a lambda, a delegate has to be created, which is an unnecessary allocation in this case. Local functions are really just functions, no delegates are necessary.

    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.

    This also means calling local functions is cheaper and they can be inlined, possibly increasing performance even further.

  2. Local functions can be recursive.

    Lambdas can be recursive too, but it requires awkward code, where you first assign null to a delegate variable and then the lambda. Local functions can naturally be recursive (including mutually recursive).

  3. Local functions can be generic.

    Lambdas cannot be generic, since they have to be assigned to a variable with a concrete type (that type can use generic variables from the outer scope, but that's not the same thing).

  4. Local functions can be implemented as an iterator.

    Lambdas cannot use the yield return (and yield break) keyword to implement IEnumerable<T>-returning function. Local functions can.

  5. Local functions look better.

    This is not mentioned in the above quote and might be just my personal bias, but I think that normal function syntax looks better than assigning a lambda to a delegate variable. Local functions are also more succinct.

    Compare:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;
    
svick
  • 236,525
  • 50
  • 385
  • 514
  • 37
    I would like to add that local functions have parameter names on the caller side. Lambdas don't. – Lensflare Apr 03 '17 at 08:46
  • 3
    @Lensflare It's true that parameter names of lambdas are not preserved, but that's because they have to be converted to delegates, which have their own names. For example: `Func f = (x, y) => x + y; f(arg1:1, arg2:1);`. – svick Apr 04 '17 at 10:39
  • 1
    Great list! However, I can imagine how the IL/JIT compiler could perform all optimizations mentioned in 1. also for delegates if their usage adheres to certain rules. – Marcin Kaczmarek Feb 20 '18 at 09:48
  • @MarcinKaczmarek If you think that optimization would be actually worth adding, you might want to create an issue about it on the [Roslyn](https://github.com/dotnet/roslyn/) or [CoreCLR](https://github.com/dotnet/coreclr/) repo. – svick Feb 20 '18 at 13:45
  • "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" - why can't lambdas use a struct? – Casebash Mar 01 '18 at 13:22
  • 1
    @Casebash Because lambdas always use a delegate and that delegate holds the closure as an [`object`](https://learn.microsoft.com/en-us/dotnet/api/system.delegate.target). So, lambdas could use a struct, but it would have to be boxed, so you would still have that additional allocation. – svick Mar 01 '18 at 13:38
  • I always like it when C# reuses syntax to do a similar thing to keep things from being unnecessarily complicated, like how they made the tuple syntax the exact same as formal parameters to try and mirror it. – AustinWBryan Apr 23 '18 at 08:25
  • 1
    @svick Great answer! One question: when would you do the opposite, choose lambda instead of local functions? – happybits Mar 25 '19 at 13:57
  • 1
    @happybits Mostly when you don't need to give a name to it, like when you're passing it to method. – svick Mar 25 '19 at 17:07
  • are there any potential performance hits (perhaps inability to optimize) to make a local helper function over a regular helper method? – Honinbo Shusaku Aug 21 '19 at 17:56
  • 1
    @Abdul No, if the local function and the method are the same, there should be no performance differences between them. Though if the local function captures variables instead of accepting them as parameters, there could be minuscule differences. – svick Aug 21 '19 at 20:58
109

In addition to svick's great answer there is one more advantage to local functions:
They can be defined anywhere in the function, even after the return statement.

public double DoMath(double a, double b)
{
    var resultA = f(a);
    var resultB = f(b);
    return resultA + resultB;

    double f(double x) => 5 * x + 3;
}
Community
  • 1
  • 1
Tim Pohlmann
  • 4,140
  • 3
  • 32
  • 61
  • 7
    This is really useful, as I can getting used to putting all the helper functions in a `#region Helpers` at the bottom of the function, so to avoid clutter within that function and espically avoid clutter in the main class. – AustinWBryan Apr 23 '18 at 08:23
  • 1
    I also appreciate this. It makes the main function that you're looking at easier to read, as you don't need to look around to find where it starts. If you want to see the implementation details, keep looking past the end. – Remi Despres-Smyth Jul 17 '19 at 17:10
  • 13
    if your functions are so big they need regions in them, they're too big. – ssmith Dec 11 '19 at 04:13
  • 1
    @ssmith Not necessarily. With modern C# features you can code using non-OOP techniques -- i.e. in a functional manner or even an old school, procedural-style way, and one of the ways to do it would be putting the body of the entire application into the one static Main() method. Frankly -- that's what C# 9's top level statements do under the hood. – Ruslan Apr 05 '22 at 19:44
  • @Ruslan Yeah, I'll stand by my original statement. Yes, you can make 1000+ line long main() methods using top level statements. That doesn't mean you should. For example, this one is "only" 540 lines but it's still a total mess trying to find anything in it (for me, at least). https://github.com/DamianEdwards/MinimalApiPlayground/blob/main/src/MinimalApiPlayground/Program.cs – ssmith Apr 06 '22 at 20:57
  • Untrue, but cool nonetheless. They still need to be in the same scoping block as where they are used (else they wouldn't warrant the name local). – Paul Childs Mar 02 '23 at 00:21
14

If you also wonder how to test local function you should check JustMock as it has the functionality to do it. Here is a simple class example that will be tested:

public class Foo // the class under test
{ 
    public int GetResult() 
    { 
        return 100 + GetLocal(); 
        int GetLocal () 
        { 
            return 42; 
        } 
    } 
}

And here is how the test looks:

[TestClass] 
public class MockLocalFunctions 
{ 
    [TestMethod] 
    public void BasicUsage() 
    { 
        //Arrange 
        var foo = Mock.Create<Foo>(Behavior.CallOriginal); 
        Mock.Local.Function.Arrange<int>(foo, "GetResult", "GetLocal").DoNothing(); 

        //Act 
        var result = foo. GetResult(); 

        //Assert 
        Assert.AreEqual(100, result); 
    } 
} 

Here is a link to JustMock documentation.

Disclaimer. I am one of the developers responsible for JustMock.

Tim Pohlmann
  • 4,140
  • 3
  • 32
  • 61
Mihail Vladov
  • 498
  • 4
  • 9
4

I was curious how much exactly local functions are faster than lambda's, so I wrote a little benchmark:

[Benchmark]
public void TestLambda()
{
    Func<int, int> _Square = (x) => x * x;
    _Square(123);
}

[Benchmark]
public void TestLocalFunc()
{
    int _Square(int x) => x * x;
    _Square(123);
}

And I was stunned by the results.

4 orders of magnitude faster!

|        Method |      Mean |     Error |    StdDev |    Median | Allocated |
|-------------- |----------:|----------:|----------:|----------:|----------:|
|    TestLambda | 1.4949 ns | 0.1997 ns | 0.0109 ns | 1.4898 ns |         - |
| TestLocalFunc | 0.0008 ns | 0.0237 ns | 0.0013 ns | 0.0000 ns |         - |

// * Warnings *
ZeroMeasurement
  BenchMark.TestLocalFunc: ShortRun -> The method duration is indistinguishable from the empty method duration
Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
0

I use inline functions to avoid garbage collection pressure specially when dealing with longer running methods. Say one would like to get 2 years or market data for a given ticker symbol. Also, one can pack a lot of functionality and business logic if one needs to.

what one does is open a socket connection to the server and loop over the data binding an event to a event. One can think of it the same way as a class is designed, only one is not writing helper methods all over the place that are really only working for one pice of functionality. below is some sample of how this might look like, please note that i am using variables and the "helper" methods are below the finally. In the Finally I nicely remove the event handlers, if my Exchange class would be external/injected i would not have any pending event handler registrated

void List<HistoricalData> RequestData(Ticker ticker, TimeSpan timeout)
{
    var socket= new Exchange(ticker);
    bool done=false;
    socket.OnData += _onData;
    socket.OnDone += _onDone;
    var request= NextRequestNr();
    var result = new List<HistoricalData>();
    var start= DateTime.Now;
    socket.RequestHistoricalData(requestId:request:days:1);
    try
    {
      while(!done)
      {   //stop when take to long….
        if((DateTime.Now-start)>timeout)
           break;
      }
      return result;

    }finally
    {
        socket.OnData-=_onData;
        socket.OnDone-= _onDone;
    }


   void _OnData(object sender, HistoricalData data)
   {
       _result.Add(data);
   }
   void _onDone(object sender, EndEventArgs args)
   {
      if(args.ReqId==request )
         done=true;
   } 
}

You can see the advantages as mentioned below, here you can see a sample implementation. Hope that helps explaining the benefits.

Walter Verhoeven
  • 3,867
  • 27
  • 36
  • 2
    1. That's a really complex example and explanation just to demonstrate local functions. 2. Local functions don't avoid any allocations when compared with lambdas in this example, because they still have to be converted to delegates. So I don't see how they would avoid GC. – svick Aug 23 '18 at 23:23
  • 2
    not passing /copying variables around, svick's answer covers the rest really well. No need to duplicate his answer – Walter Verhoeven Aug 24 '18 at 13:22