Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning local functions to delegates

Tags:

c#

c#-7.0

In C# 7.0 you can declare local functions, i.e. functions living inside another method. These local functions can access local variables of the surrounding method. Since the local variables exist only while a method is being called, I wondered whether a local function could be assigned to a delegate (which can live longer than this method call).

public static Func<int,int> AssignLocalFunctionToDelegate()
{
    int factor;

    // Local function
    int Triple(int x) => factor * x;

    factor = 3;
    return Triple;
}

public static void CallTriple()
{
    var func = AssignLocalFunctionToDelegate();
    int result = func(10);
    Console.WriteLine(result); // ==> 30
}

It does in fact work!

My question is: why does this work? What is going on here?

like image 640
Olivier Jacot-Descombes Avatar asked Dec 13 '16 20:12

Olivier Jacot-Descombes


People also ask

How do you call a delegate function?

You can invoke (or call) the method through the delegate instance. public delegate int PerformCalculation(int x, int y); Any method from any accessible class or struct that matches the delegate type can be assigned to the delegate. The method can be either static or an instance method.

What is a function delegate?

A delegate is a type-safe function pointer that can reference a method that has the same signature as that of the delegate. Delegates are used to define callback methods and implement event handling, and they are declared using the “delegate” keyword.

Why delegates why not call methods directly?

If you think of delegates as being similar to interface definitions for a specific type of method, you can start to see why delegates exist. They allow clients of our delegates to ignore all the details of their implementations - even their names!

How do you call a delegate function in C#?

Declaration of Delegates Delegate type can be declared using the delegate keyword. Once a delegate is declared, delegate instance will refer and call those methods whose return type and parameter-list matches with the delegate declaration.


1 Answers

This works because the compiler creates a delegate which captures the factor variable in a closure.

In fact if you use a decompiler, you'll see that the following code is generated:

public static Func<int, int> AssignLocalFunctionToDelegate()
{
    int factor = 3;
    return delegate (int x) {
        return (factor * x);
    };
}

You can see that factor will be captured in a closure. (You are probably already aware that behind the scenes the compiler will generate a class that contains a field to hold factor.)

On my machine, it creates the following class to act as a closure:

[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
    // Fields
    public int factor;

    // Methods
    internal int <AssignLocalFunctionToDelegate>g__Triple0(int x)
    {
        return (this.factor * x);
    }
}

If I change AssignLocalFunctionToDelegate() to

public static Func<int, int> AssignLocalFunctionToDelegate()
{
    int factor;
    int Triple(int x) => factor * x;
    factor = 3;
    Console.WriteLine(Triple(2));
    return Triple;
}

then the implementation becomes:

public static Func<int, int> AssignLocalFunctionToDelegate()
{
    <>c__DisplayClass1_0 CS$<>8__locals0;
    int factor = 3;
    Console.WriteLine(CS$<>8__locals0.<AssignLocalFunctionToDelegate>g__Triple0(2));
    return delegate (int x) {
        return (factor * x);
    };
}

You can see that it is creating an instance of the compiler-generated class for use with the Console.WriteLine().

What you can't see is where it actually assigns 3 to factor in the decompiled code. To see that, you have to look at the IL itself (this may be a failing in the decompiler I'm using, which is fairly old).

The IL looks like this:

L_0009: ldc.i4.3 
L_000a: stfld int32 ConsoleApp3.Program/<>c__DisplayClass1_0::factor

That's loading a constant value of 3 and storing it in the factor field of the compiler-generated closure class.

like image 184
Matthew Watson Avatar answered Sep 28 '22 04:09

Matthew Watson