📜 ⬆️ ⬇️

C # closures

Before reading the article, answer the following question - what will be printed after the execution of the following code?

P p = Console.WriteLine; // P is declared as delegate void P ();
foreach (var i in new [] {1, 2, 3, 4}) {
p + = () => Console.Write (i);
}
p ();

(Unfortunately, not enough karma for a normal design)

I conducted a survey among my colleagues, and only three people out of ten were able to answer correctly, and only two knew exactly what was happening and why. I, to my shame, did not know the correct answer.
So, the code translated above will show 4444 . However, if this code is slightly modified:
')
P p = Console.WriteLine;
foreach (var i in new int [] {1, 2, 3, 4}) {
int j = i;
p + = () => Console.Write ( j );
}
p ();
... then the result will be 1234 . Let's see why this happens.

Our anonymous method (lambda expression is just “syntactic sugar” for anonymous methods) uses an external variable in the body. This variable becomes captured , and its lifetime is increased to the lifetime of the delegate that uses it. This allows the method to basically use the values ​​of the captured variables.

Now instantiation of variables and their scope are involved . In the first case, the variable i instantiated once before foreach . Actual code

foreach (var i in new [] {1, 2, 3, 4}) {
// ...
}
is equivalent to

{
int i;
foreach (i in new [] {1, 2, 3, 4}) {
// ...
}
}
In the second case, the variable j is created and instantiated inside the loop at each iteration. Variables are closed in their scope. Thus, in the first case, the closed variable i will change with each iteration and by the end of the cycle it will be equal to four. That is why the delegate will print four fours. In the second case, j will be closed within the scope of the loop and will be unchanged (in fact, four instances of the variable j will be created, each of which will receive its value), and the delegate will print 1234 .

All this becomes quite obvious if we look inside the code generated by the compiler, for example, with the help of Reflector. The compiled code of the first example (after some combing) looks like this:

class DecodedFoo {
private delegate void P ();
class Anonim {
public int i;
public void p ()
{
Console.Write (i);
}
}
public void Print () {
P p = Console.WriteLine;
var a = new Anonim ();
var array = new [] {1, 2, 3, 4};
for (var i = 0; i <array.Length; i ++) {
ai = array [i];
p + = ap;
}
p ();
}
}
Interestingly, the compiler has deployed a foreach in for .

The second example will look like this:

class DecodedBar {
private delegate void P ();
class Anonim {
public int j;
public void p () {
Console.Write (j);
}
}
public void Print () {
P p = Console.WriteLine;
foreach (var i in new [] {1, 2, 3, 4}) {
var a = new Anonim ();
aj = i;
p + = ap;
}
p ();
}
}
Related links for those interested:
http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx

http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

PS Resharper 4.0 is able to determine such cases, and for the first example it issues a warning “Access to modified closure” and offers to remake the first example into the second. But, however, he does not know how to separate cases when a delegate is called inside a loop, from cases when a delegate is called out of a loop.

Source: https://habr.com/ru/post/36601/


All Articles