A closure is a type of function that is tied to the code that defines it. This enables closure functions to use variables from the reference environment, despite the fact that these values are outside the scope of the closure. The external variables that the function uses are “closed over” when it is formed. This means that they are bound to the closure function in a way that makes them accessible.
Example 1:
var c = CreateCounter();
Console.WriteLine(c());
Console.WriteLine(c());
Console.WriteLine(c());
Console.WriteLine(c());
Console.WriteLine(c());
Func<int> CreateCounter()
{
int c = 0;
return () => c = c + 1;
}
The CreateCounter is called several times; after each call, the state (value of c) is retained.
Output:
$ dotnet run
1
2
3
4
5
Example 2:
Closures are often used in LINQ code.
var vals = new int[] {-1, -2, 0, 1, 5, 3};
var limit = 0;
Func<int, bool> greaterThan = e => e > limit;
var res = vals.Where(greaterThan);
foreach (var e in res)
{
Console.WriteLine(e);
}
In the example, we define a filter delegate. The limit variable that is used in the predicate is a free variable defined outside of the predicate definition.
Output:
$ dotnet run
1
3
5
Closure and out-of-scope variables
Since the capture of variables by the closure is not quite obvious in the anonymous method or lambda expression instances, the results are as you might expect. We can make this more obvious by modifying the delegate’s scope.
Consider the code below. The closure is stored in a class-level Action variable in this case. Before executing the closure, the main function calls the setclosure method to initialize it. It’s crucial to use the setclosure function. The integer variable is created, initialized, and then utilized within the closure, as you can see. This integer variable is no longer in scope when the setclosure procedure is completed. However, we continue to invoke the delegate after this occurs. The end effect is no longer so evident.
Is it going to compile and run properly?
Will there be an error if you try to access an out-of-scope variable?
Let’s find out.
Output:
10+10=20
As you can see, we got the same result as in the first example. This is an example of closure in action. The delegate code seized, or “closed over,” the “nonLocal” variable, allowing it to remain in scope beyond the customary bounds. It will, in reality, be available until there are no more references to the delegate.
Although we’ve seen closures work, C# and the.NET framework don’t truly support them. What truly happens is that the compiler does some behind-the-scenes work. The compiler creates a new, hidden class that contains the non-local variables and the code you include in the anonymous function or lambda expression when you build your project. Non-local variables are expressed as fields in the code, which is included in a method. When the delegate is run, the method of this new class is invoked.
A simple closure’s automatically produced class looks like this:
Closure captures variable, not value
When closures are defined, certain programming languages save the values of variables used in those closures. The variables are captured by C#. This is a crucial distinction since it can cause values to shift over the scope border. Consider the following code as an example. We’re going to make a closure that outputs our well-known mathematical equation. The integer variable’s value is 1 when the delegate is declared. The variable’s value changes to 5 after the closure is defined but before it is executed.
Since the non-local variable has a value of 1 at the time the closure is constructed, you might anticipate the output to be “1 + 1 = 2.” This is exactly what would happen with various programming languages. However, since the variable is recorded, any change in its value has an impact on the closure’s execution.
The final result is as follows:
5+1=6
Changes to a non-local closure variable are also passed back and forth. The delegate in the following code modifies the value before the declaring code displays it. Despite being in a different scope than the closure, the change is apparent in the external code.
Output:
2
Variable capture exposes our programs to the risk of introducing unanticipated bugs. Another example might be used to highlight this issue. We’re going to use closures in a more common scenario this time: multi-threaded or parallel programming. A for loop is seen below, which creates and starts five new threads. Before displaying the value of the loop’s control variable, each stops briefly. If the control variable’s value was captured, the numbers one through five would be written to the console, though potentially not in the correct order. We see the final value of 6 for each thread since the variable is bound to the closure and the loop completes before the threads produce their messages.
Fortunately, this type of issue is simple to resolve once you realize that variables, not values, are being collected. For each iteration of the loop, we simply need to introduce a new variable instance. This can be declared in the body of the loop and given the control variable’s value. When the loop ends, the temporary variable would normally go out of scope, but the closure will bind to it and keep it alive.
The code below creates five instances of the “value” variable, each with a distinct value and tied to a different thread.
Resource: ParTech, Medium
The Tech Platform
Comments