Explanation of closure concept in C #

Time:2021-10-18

Understanding closures in C #

1. Meaning of closure

First, closure is not a concept for a specific language, but a general concept. We will come into contact with it except in various languages that support functional programming. Some languages that do not support functional programming can also support closures (such as anonymous inner classes before Java 8).

Among the definitions of closures I’ve read, I think it’s clear in the book JavaScript advanced programming. The specific definitions are as follows:

A closure is a function that has access to a variable in the scope of another function.

Note that the term closure itself refers to a function. A common way to create such a special function is to create another function in one function.

2. Using closures in C # (an example is selected from C # functional programming)

Let’s use a simple example to understand c# closure


class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GetClosureFunction()(30));
    }

    static Func<int, int> GetClosureFunction()
    {
        int val = 10;
        Func<int, int> internalAdd = x => x + val;

        Console.WriteLine(internalAdd(10));

        val = 30;
        Console.WriteLine(internalAdd(10));

        return internalAdd;
    }
}

The execution process of the above code is that the main function calls the getclosefunction function, which returns the delegate internaladd and is executed immediately.

The output results are 20, 40 and 60 in turn

It corresponds to the concept of closure proposed at the beginning. The delegate internaladd is a closure that references the variable Val in the scope of the external function getclosefunction.

Note: whether internal add is treated as a return value has nothing to do with the definition of closure. Even if it is not returned externally, it is still a closure.

3. Understand the implementation principle of closures

Let’s analyze the execution process of this code. At the beginning, the function getclosefunction defines a local variable Val and a delegate internaladd created using lamdba syntax sugar.

The first execution of the delegate internaladd 10 + 10 outputs 20

Then, the local variable value Val referenced by internaladd is changed, the delegate is executed again with the same parameters, and the output is 40. Obviously, the change of local variables affects the execution result of the delegate.

Getclosefunction returns internaladd to the outside, takes 30 as the parameter, and executes it. The result is 60, which is consistent with the last value 30 of Val local variable.

Val as a local variable. Its life cycle should have ended after the getclosefunction was executed. Why will it affect the subsequent results?

We can decompile to see what the compiler does for us.

In order to increase readability, the following code modifies the name generated by the compiler and arranges the code appropriately.


class Program
{
    sealed class DisplayClass
    {
        public int val;

        public int AnonymousFunction(int x)
        {
            return x + this.val;
        }
    }

    static void Main(string[] args)
    {
        Console.WriteLine(GetClosureFunction()(30));
    }

    static Func<int, int> GetClosureFunction()
    {
        DisplayClass displayClass = new DisplayClass();
        displayClass.val = 10;
        Func<int, int> internalAdd = displayClass.AnonymousFunction;

        Console.WriteLine(internalAdd(10));

        displayClass.val = 30;
        Console.WriteLine(internalAdd(10));

        return internalAdd;
    }
}

The compiler creates an anonymous class (if you do not need to create a closure, the anonymous function will only exist in the same class as getclosefunction, and the delegate instance will be cached, see page 362 of CLR via c# Fourth Edition), and creates its instance in getclosefunction. Local variables actually exist as fields in anonymous classes.

4. C#7 the optimization of closures not as return values

If you write the code in Section 2 in vs2017. You will get a prompt asking whether to convert lambda expressions (anonymous functions) to local functions. Native functions are c#7 provided with a new syntax. So what’s the difference between using local functions to implement closures?

If the code is the same as that in Section 2, change to the local function and check the IL code. In fact, nothing will change.


class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GetClosureFunction()(30));
    }

    static Func<int, int> GetClosureFunction()
    {
        int val = 10;
        int InternalAdd(int x) => x + val;

        Console.WriteLine(InternalAdd(10));

        val = 30;
        Console.WriteLine(InternalAdd(10));

        return InternalAdd;
    }
}

However, when the internal add does not need to be returned, the result is different.

Let’s take a look at the demo code and compiled decompiled code when anonymous functions and local functions create closures that are not used as return values.

Anonymous function


static void GetClosureFunction()
{
    int val = 10;
    Func<int, int> internalAdd = x => x + val;

    Console.WriteLine(internalAdd(10));

    val = 30;
    Console.WriteLine(internalAdd(10));
}

Compiled decompile code


sealed class DisplayClass
{
    public int val;

    public int AnonymousFunction(int x)
    {
        return x + this.val;
    }
}

static void GetClosureFunction()
{
    DisplayClass displayClass = new DisplayClass();
    displayClass.val = 10;
    Func<int, int> internalAdd = displayClass.AnonymousFunction;

    Console.WriteLine(internalAdd(10));

    displayClass.val = 30;
    Console.WriteLine(internalAdd(10));
}

Local function

class Program
{
    static void Main(string[] args)
    {
    }

    static void GetClosureFunction()
    {
        int val = 10;
        int InternalAdd(int x) => x + val;

        Console.WriteLine(InternalAdd(10));

        val = 30;
        Console.WriteLine(InternalAdd(10));
    }
}

Compiled decompile code

//Change point 1: change from the original class to struct
struct DisplayClass
{
    public int val;

    public int AnonymousFunction(int x)
    {
        return x + this.val;
    }
}

static void GetClosureFunction()
{
    DisplayClass displayClass = new DisplayClass();
    displayClass.val = 10;

    //Change point 2: no longer build delegate instances, but directly call instance methods of value types
    Console.WriteLine(displayClass.AnonymousFunction(10));

    displayClass.val = 30;
    Console.WriteLine(displayClass.AnonymousFunction(10));
}

The above two changes can improve the performance to a certain extent. The current understanding is to replace the class with a structure, and the structure instance can be released immediately after the method runs without waiting for garbage collection. Therefore, in the official recommendation, if the use of delegation is not necessary, it is more recommended to use local functions rather than anonymous functions.

This is the end of this article on explaining the concept of closure in c# and for more information about c# closure, please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!

Recommended Today

Swift bottom layer exploration (III): pointer

IOS memory partition image.png Stack The stack area is to store the current local variables and the context during the operation of the function. func test() { var age: Int = 10 print(age) } test() (lldb) po withUnsafePointer(to: &age){print($0)} 0x00007ffeefbff3d8 0 elements (lldb) cat address 0x00007ffeefbff3d8 &0x00007ffeefbff3d8, stack address (SP: 0x7ffeefbff3b0 FP: 0x7ffeefbff3e0) SwiftPointer.test() -> […]