Closures In C# Demystified

Abstract. This is tutorial text on “function closures” in C#. Some theory is explained, and several C# examples are shown.

Introduction

Function closures” are tricky and sometimes difficult to understand. They are possible and present in C# language. Every ambitious C# developer should be familiar with function closures, either to use them or to competently read/debug other people's code. They offer fancy encapsulation in the form of a “captured variable” that is not so easy to comprehend. This tutorial tries to explain it and provide a sufficient number of examples. The intended audience is Intermediate C# developer and above.

While many might disagree, from the readability of the code point of view, I would argue that it is more readable to see classical encapsulation via a class than a “function closure” in the code. But of course, opinions differ, and many like to use the fancy staff.

Theoretical background

Before some practical examples, some theoretical background is needed. In this article, we will not go for the full rigor in definitions, like one that can be found in a very good article [1]. We are more focused on explaining and comprehending the basic concepts, leaving a more formal approach to further reading of materials like references enclosed.

Simplified definitions

Here are some plain language explanations.

How to create closures in C#?

Closures are created when you are inside a function/method in the C# reference variable from the “above scope,” and then you pass that function as a delegate around. That variable from the “above scope” is passed around with that function delegate.

How is the closure implemented?

When the compiler notices that you are inside a function accessing the variable from the above scope, it creates a record in which it stores 1) the function in question, 2) the variable from the “above scope& (popularly called captured variable) and passes them around together.

Analogy model for thinking about implementation

One very good way to think about it and represent it in your mind is that the compiler creates a private class in which he encapsulates that variable from the “above scope” (so-called “captured variable”) and transforms a “function closure” into a method of that class and passes that private class around.

More formal definitions

Here are more formal definitions, still not as formal as [1]. You can skip this in the first reading.

A programming language with first-class functions.

A programming language is said to have “First-class functions” if it treats functions as first-class citizens, meaning that functions can be assigned to variables, passed as arguments to another function, etc. C# is definitely such a language since delegates can be assigned to variables and passed around. Basically, it says that if you can somehow obtain a “pointer to a function” ( in C/C++ terminology) and pass it around, that is a special feature of that language, which we call “first-class function language”. In such languages, typically, “function closure” concepts have sense and are possible. So, the concept of “function closure” has no sense in every programming language, just in some languages, and C# is one of them.

Free variable in C#

That is a variable that is used locally but defined in the enclosing scope. That is what we sometimes call “variable from the above scope”.

Lexical scope in C#

That is the definition area of an expression. For the variable, that is an area in which the variable is defined/created. It is often called “static scope ” since it can be deduced from static program text.

What are closures in C#

The “function closure” is the concept of implementing lexically scoped variable binding in a language with first-class functions. Practically, that means that “function closure” is storing a record (structure) of a function together with references/values of any free variables it has.

When closures are implemented?

Closures are typically implemented as a special data structure that contains 1) a pointer to the function code; and 2) a representation of any “free variable” at the time of closure creation. The second part is sometimes referred to as the “function lexical environment”.

Example 1. created, using local function syntax.

using System;
public delegate void MyAction();
public class TestClass
{
    public static MyAction CreateClosureFunction()
    {
        // We created a scope for this variable             
        int i_capturedVariable = 0;

        // Using local function syntax
        void ff()
        {
            // This is a variable from the "above scope"
            // and it will be captured
            ++i_capturedVariable;
            Console.WriteLine("i_capturedVariable: " + i_capturedVariable);
        }
        return ff;
    }
}
class Program
{
    static void Main(string[] args)
    {
        MyAction? ClosureFunction1 = TestClass.CreateClosureFunction();
        // Here the scope is closed, and the variable
        // i_capturedVariable no longer exists
        Console.WriteLine("ClosureFunction1 invocation:");
        ClosureFunction1();
        ClosureFunction1();
        ClosureFunction1();
    }
}

The above code is an example of a “function closure” in C#.

It is defined in “above scope” to the function scope where it is used. In the above terminology, that is a “free variable,” and it will be bound to the function during assignment to the delegate ClosureFunction1. In the above terminology, it will become a “captured variable”. That variable is not in the function scope but is used inside the function, so when the function is passed around, it needs to be encapsulated with the function. The existence of such a variable is the main and only reason why “function closure” needs to be created. The main trick here is that at the moment in which the function is invoked/executed, the scope in which the variable i_capturedVariable is defined will no longer exist, so to make the function work, the compiler needs to encapsulate that variable with the function itself.

What is delegate ClosureFunction1?

Function closures in C# are created only when the function is passed around via a delegate, which is analogous to the C/C++ “pointer to a function”.

What is an assignment to the delegate ClosureFunction1?

It looks like an ordinary assignment to the delegate; nothing in the code visibly indicates that the “function closure” is being created and assigned. All work is done by the compiler in the background. That is why is sometimes not easy to recognize that “function closure” is being created. Only by decompiling the C# code one can see that the variable i_capturedVariable is being encapsulated and passed together with the function.

What is the execution result?

What we see is not only that “function closure” has access to the variable i_capturedVariable from the above scope, although that scope no longer exists, but also that it can use that variable to remember the state between invocations. That is why we say that the variable i_capturedVariable from the “above scope” is a “captured variable”.

Result of invocation/execution of closure function

The fact that now “function closure,” represented by delegate ClosureFunction1, carries its own state encapsulated within itself is a feature that is often a motivation for the creation and usage of “function closures”. That is a fancy way to encapsulate state with the function and is liked by many programmers.

Anonymous function syntax

Here is a practical example of C# “function closure” created using anonymous function syntax.

using System;
public delegate void MyAction();
class Program
{
    static void Main(string[] args)
    {
        MyAction? ClosureFunction1 = null;

        {
            // We created a scope for this variable             
            int i_capturedVariable = 0;
            // Using anonymous function syntax
            ClosureFunction1 = delegate
            {
                // This is a variable from the "above scope"
                // and it will be captured
                ++i_capturedVariable;
                Console.WriteLine("i_capturedVariable: " + i_capturedVariable);
            };
        }
        // Here the scope is closed, and the variable
        // i_capturedVariable no longer exists
        Console.WriteLine("ClosureFunction1 invocation:");
        ClosureFunction1();
        ClosureFunction1();
        ClosureFunction1();
    }
}

The above code is an example of a “function closure” in C#. Even though we, this time, used “anonymous function syntax”, all the comments made in Example 1 (paragraph 3) still apply and stay the same.

Lambda expression syntax

Here is a practical example of C# “function closure” created using lambda expression syntax.

using System;
public delegate void MyAction();
class Program
{
    static void Main(string[] args)
    {
        MyAction? ClosureFunction1 = null;

        {
            // We created a scope for this variable             
            int i_capturedVariable = 0;
            // Using lambda expression syntax
            ClosureFunction1 = () =>
            {
                // This is a variable from the "above scope"
                // and it will be captured
                ++i_capturedVariable;
                Console.WriteLine("i_capturedVariable: " + i_capturedVariable);
            };
        }
        // Here the scope is closed, and the variable
        // i_capturedVariable no longer exists
        Console.WriteLine("ClosureFunction1 invocation:");
        ClosureFunction1();
        ClosureFunction1();
        ClosureFunction1();
    }
}

The above code is an example of a “function closure” in C#. Even though we, this time, used “lambda expression syntax”, all the comments made in Example 1 (paragraph 3) still apply and stay the same.

The same captured variable, but not shared

The question that is arising is: If we have 2 instances of the same “function closure”, do they reference the same “captured variable,” or each has its own instance? Here is the answer.

using System;

public delegate void MyAction();

public class TestClass
{
    public static MyAction CreateClosureFunction()
    {
        // We created a scope for this variable             
        int i_capturedVariable = 0;
        // Using local function syntax
        void ff()
        {
            // This is a variable from the "above scope"
            // and it will be captured
            ++i_capturedVariable;
            Console.WriteLine("i_capturedVariable: " + i_capturedVariable);
        }
        return ff;
    }
}
class Program
{
    static void Main(string[] args)
    {
        MyAction? ClosureFunction1 = TestClass.CreateClosureFunction();
        MyAction? ClosureFunction2 = TestClass.CreateClosureFunction();
        // Here the scope is closed, and the variable
        // i_capturedVariable no longer exists
        Console.WriteLine("ClosureFunction1 invocation:");
        ClosureFunction1();
        ClosureFunction1();
        ClosureFunction1();
        Console.WriteLine("ClosureFunction2 invocation:");
        ClosureFunction2();
        ClosureFunction2();
        ClosureFunction2();
    }
}

So, from the result of the execution, we see that each instance of the “function closure” has its own instance of the “captured variable”. Precisely speaking, that is true for this example. It is not necessary to always be like that. The key thing to notice is that every time TestClass.CreateClosureFunction() is executed, a new instance of i_capturedVariable is created, and that is the reason why each function closure has its own instance of the captured variable.

Decompiling (function closure) in C#

It is always interesting to see how the C# compiler solves the problem with the compilation of “function closure”. I used JetBrains dotPeek to decompile above Example 3 (since it is quite simple) into option “Low-Lewel C#” code. Here is what Decompiler gave as a result.

decompiling function

decompiling function closures

using System;
namespace Example3
{
    internal class Program
    {
        [NullableContext(1)]
        private static void Main(string[] args)
        {
            var cDisplayClass10 = new DisplayClass1_0();
            cDisplayClass10.i_capturedVariable = 0;
            MyAction myAction = new MyAction(cDisplayClass10, DisplayClass1_0.<Main>b__0);
            Console.WriteLine("ClosureFunction1 invocation:");
            myAction();
            myAction();
            myAction();
        }
        public Program()
        {
        }
        public delegate void MyAction();
        private sealed class DisplayClass1_0
        {
            public int i_capturedVariable;
            public DisplayClass1_0()
            {
            }
            internal void <Main>b__0()
            {
                ++this.i_capturedVariable;
                Console.WriteLine("i_capturedVariable: " + this.i_capturedVariable.ToString());
            }
        }
    }
}

Please notice that the compiler created “private sealed class <>c__ DisplayClass1_0” to encapsulate i_capturedVariable and created method <Main>b__0() to represent lambda expression. Above in the “Main” method, it translated function capture invocation into class methods/attributes manipulation.

The same captured variable shared

Let us look again at the case when we have 2 instances of the same “function closure”, and they reference the same “captured variable”.

public delegate void MyAction();
static void Main(string[] args)
{
    MyAction? ClosureFunction1 = null;
    MyAction? ClosureFunction2 = null;
    {
        // We created a scope for this variable             
        int i_capturedVariable = 0;
        // Using lambda expression syntax
        ClosureFunction1 = () =>
        {
            // This is a variable from the "above scope"
            // and it will be captured
            ++i_capturedVariable;
            Console.WriteLine("i_capturedVariable:" + i_capturedVariable);
        };
        ClosureFunction2 = () =>
        {
            // This is a variable from the "above scope"
            // and it will be captured
            ++i_capturedVariable;
            Console.WriteLine("i_capturedVariable:" + i_capturedVariable);
        };
    }
    // Here the scope is closed, and the variable
    // i_capturedVariable no longer exists
    Console.WriteLine("ClosureFunction1 invocation:");
    ClosureFunction1();
    ClosureFunction1();
    ClosureFunction1();
    Console.WriteLine("ClosureFunction2 invocation:");
    ClosureFunction2();
    ClosureFunction2();
    ClosureFunction2();
}
// ==Result===========================================
/* 
ClosureFunction1 invocation:
i_capturedVariable:1
i_capturedVariable:2
i_capturedVariable:3
ClosureFunction2 invocation:
i_capturedVariable:4
i_capturedVariable:5
i_capturedVariable:6
*/

From the execution result, we can see that they reference the same “captured variable”. If you look carefully into the code, you will see that it is one instance of i_capturedVariable that is being referenced by both function closures. After looking into it for a while, it will make sense. In the next example, we will show how to overcome that issue if that is what we want.

Modifying shared captured variables into non-shared

We will now modify the slightly above example so that each instance of function closure gets its own instance of the captured variable.

public delegate void MyAction();
static void Main(string[] args)
{
    MyAction? ClosureFunction1 = null;
    MyAction? ClosureFunction2 = null;
    {
        // We created a scope for this variable             
        int i_capturedVariable1 = 0;
        // Using lambda expression syntax
        ClosureFunction1 = () =>
        {
            // This is a variable from the "above scope"
            // and it will be captured
            ++i_capturedVariable1;
            Console.WriteLine("i_capturedVariable (ClosureFunction1): " + i_capturedVariable1);
        };
    }

    {
        // We created a scope for this variable             
        int i_capturedVariable2 = 0;
        ClosureFunction2 = () =>
        {
            // This is a variable from the "above scope"
            // and it will be captured
            ++i_capturedVariable2;
            Console.WriteLine("i_capturedVariable (ClosureFunction2): " + i_capturedVariable2);
        };
    }
    // Here the scope is closed, and the variables
    // i_capturedVariable1 and i_capturedVariable2 no longer exist

    Console.WriteLine("ClosureFunction1 invocation:");
    ClosureFunction1();
    ClosureFunction1();
    ClosureFunction1();
    Console.WriteLine("ClosureFunction2 invocation:");
    ClosureFunction2();
    ClosureFunction2();
    ClosureFunction2();
}
// ==Result===========================================
/* 
ClosureFunction1 invocation:
i_capturedVariable (ClosureFunction1): 1
i_capturedVariable (ClosureFunction1): 2
i_capturedVariable (ClosureFunction1): 3
ClosureFunction2 invocation:
i_capturedVariable (ClosureFunction2): 1
i_capturedVariable (ClosureFunction2): 2
i_capturedVariable (ClosureFunction2): 3
*/

So, from the result of the execution, we see that each instance of the “function closure” has its own instance of the “captured variable”. Now the code from Example 5 is better understood.

Function closures and multithreading in C#

The question is how “function closures” behave in a multithreading environment. The answer is the same as other functions. If you look into Example 5, you will see that ClosureFunction1 and ClosureFunction2 share the same instance of the “captured variable”. As with any shared resource, if accessed from different threads, that can create the problem. The fact that shared resource is a fancy “captured variable” makes no difference.

One can easily imagine a case when 2 function closures share the same “captured variable” and are run on 2 different threads and are writing/reading to the shared resource being “captured variable” and, as a consequence, can run into all regular concurrency issues, like a race condition, etc. I will not create any example code here in order not to bloat the text.

Conclusion

Function closure is an interesting concept and technique and needs to be in the repertoire of every serious C# programmer. My personal feeling is it is a bit of “obscuring” code, and I prefer to use the class with class attribute encapsulation when possible. However, it is widely accepted and present in C# code, and one needs to understand it regardless of his/her personal preferences of using it.

References


Similar Articles