“C# Closures Explained with IL and Compiler-Generated Code”

First before we dive into the implementation , let's define some terms first.

Let’s define what is a closure first, on the world of computer science a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions.

  • lexical scope — this is a region of statements in your code where a variable is visible. A variable is visible in a statement if it can be referenced from the statement.

  • first-class functions — a programming language is said to have first-class functions when functions can be treated as any other data. In C# first-class functions are supported using anonymous methods and lambdas.

  • captured variable - a variable usually a local variable that is captured on lambda body that doesn’t exist on the lambda parameter.

Now let’s take a look at a piece of code, then from there let’s analyze what a closure is.

fig 1

Here a closure is happening. This outputs: 100. And if you are going to analyze the code. You will be confused because how come the bar1() method do have an access to the local variable i.

Now looking into code we can see that there is no parameter on the lambda () but on the body => we are trying to print i which is a local variable that is not present on the lambda parameter.

Here the captured variable is i. And here a closure happened.

Action a = () => Console.Write($”{i} “);

Now let’s define the rules first before we deep dive on what’s happening when a closure happened.
Closure Rules according to Jon Skeet’s C# in depth

1.) If no variables are captured at all, the compiler can create a static method. No extra content is required.

2.) If the only variables captured are instance fields, the compiler can create an instance method. capturing one instance field is equivalent to capturing 100 of them, because you need access only to this.

3.) If local variables or parameters are captured, the compiler creates a private nested class to contain that context and then an instance method in that class containing the lambda expression code. The method containing the lambda expression is changed to use that nested class for every access to the captured variables.

I don’t want you to be overwhelmed i just want you to read the theoretical first, Now Let’s dig dive into the realms of c#!

If we are going back to our example fig1 we have a captured variable i and according to the rules, the compiler creates a private nested class to contain that context.
If we are going to peek that using an ILSPY we are going to see a code like this.

Now next to the rule is “The method containing the lambda expression is changed to use that nested class for every access to the captured variables." according to the second rule we must substitute / use the nested class created for every access to the captured variable i.

To demonstrate this furthermore we need to create this code into our compiler itself.

Now let’s substitute all the captured variables using this private class.

Now let’s show off more examples.

This output: 10 10 10 10 10 10 10 10 10 10

Without substituting it , or knowing the concept of closure , surely this is confusing!

but take a look how it makes sense once we substitute the captured variables with the compiler generated class.

I’m going to show you more examples about the different mix of how the compiler generated class is created.

Here we are Instantiating a local variable multiple times.

Because we are creating a new context for each loop iteration

this is how we translate it. It’s because the text is declared inside the loop. So we are creating the text every loop. Each lambda expression captures a different instantiation of the variable.

An example of Capturing variables from Multiple scopes.

This creates a class that looks like this.

And using that compiler generated class , it translates to.

Note: If you use a lambda expression in a performance-critical piece of code, you should be aware of how many objects will be created to support the variables it captures.

Exception: COPIED VALUES DOESN”T PRODUCE CLOSURES.

Conclusion: Closures are confusing but once you get a hang of it , it’s really a powerful feature that you can use. I suggest digging into LINQ that’s where closures really shine.

Thank you for bearing with me!

References:
https://medium.com/swlh/the-magic-of-c-closures-9c6e3fff6ff9

https://www.simplethread.com/c-closures-explained/

https://csharpindepth.com/articles/Closures

0
Subscribe to my newsletter

Read articles from Juan Miguel Nieto directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Juan Miguel Nieto
Juan Miguel Nieto

A software developer trying to write organic blogs.