26

In C# 8.0, Static Local Functions are announced

Can anyone help enlighten me as to why you would want to declare a local function as static?

The reason given in in the article:

to ensure that local function doesn't capture (reference) any variables from the enclosing scope

But:

  1. I don't understand why would you want to ensure that?
  2. Is there any other reason or benefits to declare it static? (performance maybe?)

The example code given in the article is:

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}
fourbeatcoder
  • 1,159
  • 3
  • 13
  • 21
  • 1
    In your `Add` method, because it is static you cannot reference `x` and `y` directly, that's what your quote means. – DavidG Nov 07 '19 at 09:31
  • 2
    @DavidG But the question is: what is the benefit? – René Vogt Nov 07 '19 at 09:32
  • Good question. I can't think of a scenario where I would use this either, so I'm curious. – ThePerplexedOne Nov 07 '19 at 09:33
  • @RenéVogt Then this question is really "why avoid captured variables" and is probably a duplicate of something like [this](https://stackoverflow.com/questions/5717186/how-to-avoid-captured-variables). – DavidG Nov 07 '19 at 09:36
  • 3
    @RenéVogt What is the benefit of having static methods in a class? To prevent accessing class members. – Ivan Stoev Nov 07 '19 at 09:37
  • @IvanStoev afaik if you'd access the local variables (_not_ class members) inside `Add`, they would be passed with `ref` by the compiler. Using the `static` keyword here obviously avoids that (so it's actually not the same as the `static` method modifier). And the question is: what is the benefit in having to pass parameters ("by val") to your local method instead of passing them as `ref`. I don't see many use cases...ok `Add`can't change `x` now...but it's a _local_ function, so there shouldn't be much risk for accidently changing the wrong local variable. – René Vogt Nov 07 '19 at 09:46
  • 1
    @RenéVogt Change `int M()` to `class M` and `return Add(x, y);` to `public int Foo() { return Add(x, y); }` and you'll see what I mean. Local variables of a method are conceptually (from the scope point of view) like private members of a class. – Ivan Stoev Nov 07 '19 at 10:01

4 Answers4

28

I don't understand why would you want to ensure that?

Because it prevents you from shooting yourself in the foot. It forces the local function to be a pure function that does not modify the state of the caller.

This returns false, because the function modifies local variables of its caller:

public bool Is42()
{
    int i = 42;     
    Foo();      
    return i == 42;

    void Foo()
    {
        i = 21;
    }   
}

And this doesn't, because it doesn't even compile:

public bool Is42()
{
    int i = 42;     
    Foo();      
    return i == 42;

    static void Foo()
    {
        i = 21;
    }   
}

It prevents surprises. Of course in these simple examples the benefit isn't immediately clear, because "well it's obvious that Foo() modifies i", but in larger codebases maintained by multiple people and not properly covered by unit tests, this simple modifier prevents grief.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • Thank you. Your answer, along with @György Kőszeg's answer give a full picture of what i asked in questions 1 & 2 in terms of modifying caller state and also in terms of cost: https://stackoverflow.com/a/58746550 – fourbeatcoder Nov 07 '19 at 16:28
7

Capturing variables has a small additional cost as it will generate an internally used type where your captured variables are public fields. Consider a slightly modified example:

int M()
{
    int y = 5;
    int x = 7;
    return Add();

    int Add() => x + y;
}

It will actually translate to something like this:

int M()
{
    int y = 5;
    int x = 7;
    var capturedVars = new <>c__DisplayClass0_0 { x = x, y = y };
    return <M>g__Add|0_0(ref capturedVars);
}

[CompilerGenerated]
private struct <>c__DisplayClass0_0
{
    public int x;
    public int y;
}

[CompilerGenerated]
internal static int <M>g__Add|0_0(ref <>c__DisplayClass0_0 class_Ref1) => 
    (class_Ref1.x + class_Ref1.y);
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • There is no capturing when using local variables in local functions. They are just passed via `ref`. Capturing only occurs for anonymous methods (lambdas, delegates). – René Vogt Nov 07 '19 at 10:21
  • 1
    The produced code above is from Reflector after compiling the modified example. As you can see it captured `x` and `y` variables. But as you can see the compiler really tries to do its best and generates a `struct` and passes `ref` variables if it is possible. – György Kőszeg Nov 07 '19 at 10:24
  • 1
    Oh right, sorry. I forgot that the locals are put together in a struct that then is passed by ref. Anyway, my point was that this is not as expensive as "real" capturing, hence the benefit of static local functions is more on the "avoid changes to the variables" side than a performance gain. – René Vogt Nov 07 '19 at 11:45
  • Thank you. Your answer, along with @CodeCaster's answer give a full picture of what i asked in questions 1 & 2 in terms of modifying caller state and also in terms of cost: https://stackoverflow.com/a/58746428 – fourbeatcoder Nov 07 '19 at 16:29
  • Note that "real" capturing (i.e. with a class instead of a struct) may still occur if the function is ever passed as an `Action` or `Func`. In OP's case, if something were to accept a `Func`, we could simply pass `Add`. If `Add` were non-static and it happened to capture anything, a compiler-generated _class_ would be used. – Timo Apr 09 '21 at 08:07
  • @Timo: you are absolutely right, and that's why I've also written _"if it is possible"_ in my last comment. Generating structs and using `ref` variables is not always applicable. – György Kőszeg Apr 09 '21 at 09:11
3

This answer from CodeCaster and this separate answer from György Kőszeg individually answer different parts of my question, so I'm bringing them both together to form the full picture for the accepted answer:

For Part 1) of my question, @CodeCaster Says:

Because it prevents you from shooting yourself in the foot. It forces the local function to be a pure function that does not modify the state of the caller.

in larger codebases maintained by multiple people and not properly covered by unit tests, this simple modifier prevents grief

So Answer 1 is: Static Local Functions ensure reliable caller method state.

For Part 2) of my question, @György Kőszeg Says:

Capturing variables has a small additional cost as it will generate an internally used type where your captured variables are public fields

And he goes on to give an example of the produced compiler code via reflector.

So Answer 2 is: Static Local Functions prevent variable capturing. Variable capturing comes at a small cost. So there is a small performance boost by declaring the local function static

Community
  • 1
  • 1
fourbeatcoder
  • 1,159
  • 3
  • 13
  • 21
  • 1
    "So there is a small performance boost by declaring the local function static" No, there is not. A local function without captures behaves the same whether it is marked `static` or not. `static` just communicates to the compiler your intent to avoid captures, so it can diagnose if you fail to live up to that promise. – Ben Voigt Dec 24 '21 at 16:04
  • why would OP summarize other answers from other users as an "accepted answer"? this makes no sense. – Emil May 26 '23 at 01:37
1

I think this is just to ensure correct usage of the variables used in the local function, as the documentation says. In large and complex methods, it can prevent accidental usage of enclosing scope variables if there are variables with the same name in the local function.