C# 7 Local functions

C# 7 Local functions

C# 7 is well underway yet many .NET developers are just getting acquainted with C# 6 features even though they have been available since early 2014.

It doesn't have to be like that however. Microsofts development nowadays happens in the open and has changed into a conversation, which is much different from how it used to be. As Dustin Campbell from MS said at Build 2016

If you just watch the github repo (roslyn) you know what we are doing […]

That being said I would like to go over Local functions in this post as I find it quite interesting. The feature was initially proposed back in February 2015 by gafter as follows.

Proposal: Nested local functions and type declarations #259

Extend the languages to support the declaration of functions and types in block scope. Local functions would be capable of using captured variables from the enclosing scope.

The feature is possible to some extend in C# already by using delegates however in many dynamic- and functional languages this is widely used whereas it traditionally hasn't truly been supported in C-like languages. An example of this would be in a dynamic language such as JavaScript where using a nested function aka. local functions is very common.

function foo() {
    var x = 10;
    
    // local function
    function incrementBy(value) {
        x += value;
        return x;
    }

    var result = incrementBy(1);
    console.assert(result == x)
}

A quite amusing explanation of local functions for C# is given by Mads Torgersen in this video from Build 2016 about the future of C# @ ~13:30. Essentially what Mads says is that you can write your helper functions inside of other functions and structure your code like JavaScript if you will. The entire audience laughed. :D

I can however immediately think of an use case where I just recently had to refactor and extract a method into a helper function. As soon as I did that however I now had to pass state along to the helper function in the form of arguments. I think even in the simplest use case it could be more elegant to simply access all members of the enclosing function and not needing to pass state around.

Here is an equivalent C# example from the above JavaScript example with a local function namely IncrementBy which is nested inside the Foo method.

// method Foo
public void Foo() 
{ 
    int x = 10; 
    
    // local function IncrementBy
    int IncrementBy(int value) 
    { 
        // access member from the enclosing scope: x
        x += value;
        return x;
    } 

    int result = IncrementBy(1); 
    Debug.Assert(result == x); 
}

There is an interesting point however, regarding scope and local functions. According to the initial implementation it would require every variable used inside a local function to be definitely assigned. That would however result in not being able to define two mutually recursive local functions, in a simple way. Because how would one local function call the other before it is declared?

Well this is actually possible to do e.g.:

f(3);
int x; // x is now 3!
void f(int v) { x = v; }

But, while this type of pre-declaration manipulation is possible it requires some very shifty magic, which is not shown here. Therefore the implementation and requirement with regards to definitely assigned variables is being revised. Once that implementation is done you will be able to call your local functions before their declaration, as shown above. However local variables used in a local function must be definitely assigned at each invocation of the local function.

In JavaScript however all of this is perfectly fine due to hoisting.

foo = 2
var foo;

// is implicitly understood as:

var foo;
foo = 2;

// or

hoisted(); // logs "foo"

function hoisted() {
  console.log("foo");
}

If we take a few steps back and reflect, I mentioned that this was possible to some extend in C# already using delegates e.g.

Action myAction = () => {
    // do stuff in here...
    Console.WriteLine("Hello world");
};

This however has some functional disadvantages which local functions wouldn't otherwise have i.e. the use of:

  • generics
  • ref
  • out
  • params
  • pointers
  • recursion (sort of..)

Also note worthy for local functions is that it does not require allocating frame objects on the heap as it would for delegates, or allocating a delegate object either. It is therefore much more efficient since we are not allocating frame objects on the heap. Capturing is done into frames that are value types and that in turn means you don't get any GC pressure from using local functions.

If we dive a bit deeper we will try to verify some of the things by using the previous C# example, illustrating local functions. It would be something as follows.

public void Foo() 
{
    int x = 10; 
    int result = Foo_IncrementBy(1, ref x); 
    Debug.Assert(result == x); 
}

// this would be the local function
private static void Foo_IncrementBy(int value, ref int x) 
{
    x += value;
    return x; 
} 

It makes use of a static method and passes the parameter by reference which is what one might have done otherwise - essentially made a helper function. If we dive even deeper and inspect the IL we can actually verify the above example by seeing that it is actually what is happening.

.method public hidebysig instance void  Foo() cil managed
{
  // Code size       44 (0x2c)
  .maxstack  2
  .locals init ([0] valuetype ConsoleApplication1.Program/'<>c__DisplayClass0_0' 'CS$<>8__locals0',
           [1] int32 result)
  IL_0000:  ldloca.s   'CS$<>8__locals0'
  IL_0002:  initobj    ConsoleApplication1.Program/'<>c__DisplayClass0_0'
  IL_0008:  nop
  IL_0009:  ldloca.s   'CS$<>8__locals0'
  IL_000b:  ldc.i4.s   10
  IL_000d:  stfld      int32 ConsoleApplication1.Program/'<>c__DisplayClass0_0'::x
  IL_0012:  nop
  IL_0013:  ldc.i4.1
  IL_0014:  ldloca.s   'CS$<>8__locals0'
  IL_0016:  call       int32 ConsoleApplication1.Program::'<Foo>g__IncrementBy0_0'(int32,
                                                                                   valuetype ConsoleApplication1.Program/'<>c__DisplayClass0_0'&)
  IL_001b:  stloc.1
  IL_001c:  ldloc.1
  IL_001d:  ldloc.0
  IL_001e:  ldfld      int32 ConsoleApplication1.Program/'<>c__DisplayClass0_0'::x
  IL_0023:  ceq
  IL_0025:  call       void [System]System.Diagnostics.Debug::Assert(bool)
  IL_002a:  nop
  IL_002b:  ret
} // end of method Program::Foo

Here below I have described the opcodes for the above IL.

  • IL_0000: Load address of local variable with index indx, short form.
  • IL_0002: Initialize the value at address dest.
  • IL_0008: Do nothing (No operation).
  • IL_0009: Load address of local variable with index indx, short form.
  • IL_000b: Push num onto the stack as int32, short form.
  • IL_000d: Replace the value of field of the object obj with value.
  • IL_0012: Do nothing (No operation).
  • IL_0013: Push 1 onto the stack as int32.
  • IL_0014: Load address of local variable with index indx, short form.
  • IL_0016: Call method described by method.
  • IL_001b: Pop a value from stack into local variable 1.
  • IL_001c: Load local variable 1 onto stack.
  • IL_001d: Load local variable 0 onto stack.
  • IL_001e: Push the value of field of object (or value type) obj, onto the stack.
  • IL_0023: Push 1 (of type int32) if value1 equals value2, else push 0.
  • IL_0025: Call method described by method.
  • IL_002a: Do nothing (No operation).
  • IL_002b: Return from method, possibly with a value.

Lastly the .locals init flag at the top sets out to clear the memory and zero out the values for the struct.

And here is what's actually going on from my understanding of the IL which verifies the previous example.
I am no IL expert but conceptually this should be 'correct'. If you eat IL for breakfast, chip in!

private struct c__DisplayClass0_0 
{
    public int x;
}

public void Foo() 
{ 
    int x = 10; 
    var arg = new c__DisplayClass0_0();
    arg.x = x;

    int result = <Foo>g__IncrementBy0_0(1, ref arg); 
    Debug.Assert(result.x == x); 
}

private static int <Foo>g__IncrementBy0_0(int value, ref c__DisplayClass0_0 arg)
{
    arg.x += value;
    return arg;
}

A struct namely c__DisplayClass0_0 is actually generated by the compiler and it's properties are zeroed out. Then a static method is also created <Foo>g__IncrementBy0_0, which accepts the original argument and another one with a reference to the struct. Cool stuff!

This is the IL for the generated method <Foo>g__IncrementBy0_0.

.method assembly hidebysig static int32  '<Foo>g__IncrementBy0_0'(int32 'value',
                                                                  valuetype ConsoleApplication1.Program/'<>c__DisplayClass0_0'& A_1) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       26 (0x1a)
  .maxstack  3
  .locals init ([0] int32 V_0)
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  ldarg.1
  IL_0003:  ldfld      int32 ConsoleApplication1.Program/'<>c__DisplayClass0_0'::x
  IL_0008:  ldarg.0
  IL_0009:  add
  IL_000a:  stfld      int32 ConsoleApplication1.Program/'<>c__DisplayClass0_0'::x
  IL_000f:  ldarg.1
  IL_0010:  ldfld      int32 ConsoleApplication1.Program/'<>c__DisplayClass0_0'::x
  IL_0015:  stloc.0
  IL_0016:  br.s       IL_0018
  IL_0018:  ldloc.0
  IL_0019:  ret
} // end of method Program::'<Foo>g__IncrementBy0_0'

I think local functions are pretty cool and would be one of those nice features that would help to express your design intent clearly. At least I see a good use case for them in situations where you simply create a helper function to merely called from one function. Doing that you would now have to pass state around. Using a local function one would immediately know that the local function is a helper method of the enclosing function.

This was a nice exercise for me and I learned quite a lot and finally got to dive into the feature. Hopefully I was able to pass some of the knowledge along to you. Feel free to provide feedback and ask questions:)

//Baha