cannot understand lambda expression output [duplicate] - c#

This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed last month.
I have this code and do not understand why the out put is 22! I am afraid it should be 01!
can anyone explain what happens? if the list store a method with a parameter, so the parameters should be 0 and 1 respectively!
List<Action> list = new List<Action>();
for (int i = 0; i < 2; i++)
{
list.Add(() => Console.Write(i));
}
foreach (var it in list)
{
it();
}

It is Closure (1, 2).
In your case Console.Write(i) will use value of i in the moment of action call. You firstly increment i in for loop then in second loop you call every action in the list. In the moment of call of every action i has value 2 - so, you get 22 as output.
To get expected result you should create local copy of i and use it:
for (int i = 0; i < 2; i++)
{
var temp = i;
list.Add(() => Console.Write(temp));
}

Addition to Roma Doskoch's anwser, another approach is to avoid for.
var list = Enumerable
.Range(0, 2)
.Select<int, Action>(i => () => Console.Write(i));

Closures capture variables, not values.
In your code, the closure captures the variable i, not whatever value happens to be stored in i on each iteration. When you invoke the action, the variable i has a value of 2 (because the loop has finished) and therefore 2 will be printed out twice.
In order to avoid this, as other answers already point out, you need to create a new variable every time around as a workaround to not being able to capture values; if you declare a new variable on every iteration then the result of capturing the variable is equivalent to capturing the value because you won't be changing it on the next iteration.

Related

IEnumerable IndexOutOfRangeException

I dont know why this I'm getting System.IndexOutOfRangeException: 'Index was outside the bounds of the array.' with this code
IEnumerable<char> query = "Text result";
string illegals = "abcet";
for (int i = 0; i < illegals.Length; i++)
{
query = query.Where(c => c != illegals[i]);
}
foreach (var item in query)
{
Console.Write(item);
}
Please can someone explain what's wrong with my code.
The problem is that your lambda expression is capturing the variable i, but the delegate isn't being executed until after the loop. By the time the expression c != illegals[i] is executed, i is illegals.Length, because that's the final value of i. It's important to understand that lambda expressions capture variables, rather than "the values of those variables at the point of the lambda expression being converted into a delegate".
Here are five ways of fixing your code:
Option 1: local copy of i
Copy the value of i into a local variable within the loop, so that each iteration of the loop captures a new variable in the lambda expression. That new variable isn't changed by the rest of the execution of the loop.
for (int i = 0; i < illegals.Length; i++)
{
int copy = i;
query = query.Where(c => c != illegals[copy]);
}
Option 2: extract illegals[i] outside the lambda expression
Extract the value of illegals[i] in the loop (outside the lambda expression) and use that value in the lambda expression. Again, the changing value of i doesn't affect the variable.
for (int i = 0; i < illegals.Length; i++)
{
char illegal = illegals[i];
query = query.Where(c => c != illegal);
}
Option 3: use a foreach loop
This option only works properly with C# 5 and later compilers, as the meaning of foreach changed (for the better) in C# 5.
foreach (char illegal in illegals)
{
query = query.Where(c => c != illegal);
}
Option 4: use Except once
LINQ provides a method to perform set exclusion: Except. This is not quite the same as the earlier options though, as you'll only get a single copy of any particular character in your output. So if e wasn't in illegals, you'd get a result of "Tex resul" with the above options, but "Tex rsul" using Except. Still, it's worth knowing about:
// Replace the loop entirely with this
query = query.Except(illegals);
Option 5: Use Contains once
You can call Where once, with a lambda expression that calls Contains:
// Replace the loop entirely with this
query = query.Where(c => !illegals.Contains(c));
This happens because, although your for loop seems at first glance to be correctly bounded, each iteration captures the index in the closure that is passed to Where. one of the most useful properties of closures is that they capture by reference, enabling all sorts of powerful and sophisticated techniques. However, in this case it means that, by the time the query is executed in the ensuing foreach loop. The index has been incremented past the length of the array.
The most straightforward change to fix this is create a loop scoped copy the current value of the index loop control variable and refer to this in your closure instead of referring directly to the loop control variable.
Ex:
for (int i = 0; i < illegals.Length; i++)
{
var index = i;
query = query.Where(c => c != illegals[index]);
}
However, as has been noted by others, there are better ways to write this that void the problem entirely and they also have the virtue that they raise the level of abstraction.
For example, you can use System.Linq.Enumerable.Except
var legals = query.Except(illegals);

c# Why does this for loop with a task.run prints more than 10 times? [duplicate]

This question already has answers here:
How can I capture the value of an outer variable inside a lambda expression?
(4 answers)
Closed 6 years ago.
What is going on here? This loop most of the time just prints this:
10101010101010101010
sometimes this:
51010101010101010101
and when i debug it, it prints in order
0123456789
class Program
{
static void Main (string[] args)
{
for ( int i = 0; i < 10; i++)
{
Task.Run(( ) => Console.Write(i));
}
Console.Read();
}
}
If you have ReSharper installed it puts a little squiggle underneath the i:
with the note:
Access to modified closure
The JetBrains site gives this explanation:
This may appear to be correct but, in actual fact, only the last value of str variable will be used whenever any button is clicked. The reason for this is that foreach unrolls into a while loop, but the iteration variable is defined outside this loop. This means that by the time you show the message box, the value of str may have already been iterated to the last value in the strings collection.
(obviously their example uses a string not an int).
It "works" under debug because other things are going on and the code isn't being executed the same as it would in release.
The solution is to use a local variable:
for ( int i = 0; i < 10; i++)
{
int local = i;
Task.Run(( ) => Console.Write(local));
}
But even then it's not guaranteed to execute in the order you expect. I just tested this and got he result:
0436215897
So each value was processed, but in indeterminate order.

Local variable scope breach, C#? [duplicate]

This question already has answers here:
Using the iterator variable of foreach loop in a lambda expression - why fails?
(3 answers)
Closed 8 years ago.
I got this piece of code,
delegate void Printer();
static void Main(string[] args)
{
List<Printer> printers = new List<Printer>();
for (int i = 0; i < 10; i++)
{
printers.Add(delegate { Console.WriteLine(i); });
}
foreach (Printer printer in printers)
{
printer();
}
Console.ReadLine();
}
Here the output is '10' for ten times.
The scope of i is with in the for loop. But while we retrieve in out side that we are still getting value from i.
How is it possible?
You have modified closure. Try this:
for (int i = 0; i < 10; i++)
{
int ii = i;
printers.Add(delegate { Console.WriteLine(ii); });
}
When you use in your anonymous method access the the variable in you local scope it creates closure.
The code in the delegate is not run until it is called, which happens in the second loop. It then refers to the i which was defined within the scope of the first loop, but with it's current value - and since the first loop has been completed already, i will be 10 each time.
I believe each of the delegates you create are given the same scope as the first loop, if that makes sense. This means that each i has it's delegate as it's scope, and since each delegate is defined within the scope of the first loop, each i will also have the loop as it's scope, even if the delegate logic is called outside that scope, as in your example.
Since i is valid throughout / across several iterations of the loop, it gets updated, and is always 10 by the time the delegates get called.
This explains why the following works as a fix:
for(int i = 0; i < 10; i++)
{
var localVar = i; // Only valid within a single iteration of the loop!
printers.Add(delegate { Console.WriteLine(localVar); });
}
Let's unroll the loop:
int i=0;
printers.Add(delegate { Console.WriteLine(i); })
i=1;
printers.Add(delegate { Console.WriteLine(i); })
...
i=10;
printers.Add(delegate { Console.WriteLine(i); })
As you can see the i variable is captured within the delegate, and the delegate itself is not run until the loop ends, and the variable has achieved the last value (10).
A simple workaround is to assign the loop variable to a local helper variable
for (int i = 0; i < 10; i++)
{
var index = i;
printers.Add(delegate { Console.WriteLine(index); });
}
As for the scope issue, any captured variables have their scope (and lifetime) extended. A variable used within a lambda/delegate will not be garbage collected until the delegate itself goes out of scope - which can be a problem for large objects. Specifically, section, 7.15.5.1 of the C# 5 Specification states:
When an outer variable is referenced by an anonymous function, the
outer variable is said to have been captured by the anonymous
function. Ordinarily, the lifetime of a local variable is limited to
execution of the block or statement with which it is associated
(§5.1.7). However, the lifetime of a captured outer variable is
extended at least until the delegate or expression tree created from
the anonymous function becomes eligible for garbage collection.
Each delegate is only invoked in the foreach, after the for loop. By this time, the variable i captured by the closure is already at its final value, viz 10. You can solve like so:
for (int i = 0; i < 10; i++)
{
var cache = i;
printers.Add(delegate { Console.WriteLine(cache); });
}

Action delegate uses the last values of variables declared outside foreach loop [duplicate]

This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 2 years ago.
I have this piece of code:
int i = 0;
foreach(var tile in lib.dic.Values)
{
var ii = i;
var t = tile;
Button b = new Button( () = > { MainStatic.tile = t; } );
Checkbox c = new Checkbox( () = > { lib.arr[ii].b = !lib.arr[ii].b; } );
i++;
}
While the above code works as it should, this piece below:
int i = 0;
foreach(var tile in lib.dic.Values)
{
Button b = new Button( () = > { MainStatic.tile = tile; } );
Checkbox c = new Checkbox( () = > { lib.arr[i].b = !lib.arr[i].b; } );
i++;
}
…will always execute the delegates with the last values of i and tile variables. Why does this happen, and why do I have to make a local copy of those vars, especially non-reference type int i?
Known "issue", please check Eric's blog Closures, captured variables.
Microsof decided to go for a breaking change, and fix it in C# 5.
This is expected: when you make a lambda, compiler creates a closure. It will capture the value of a temporary variable in there, but it would not capture the value of loop variables and other variables that change after creation of the lambda.
The core of the issue is that the delegate creation and execution times are different. The delegate object is created while the loop is running, but it is called well after the loop has completed. At the time the delegate is called, the loop variable has the value that it reached at the time the loop has completed, resulting in the effect that you see (the value does not change, and you see the last value from the loop).
Forgetting to create a temporary variable for use in closures is a mistake so common that popular code analyzers (e.g. ReSharper) warn you about it.
You cannot use loop variables like this because by the time the delegate is executed the loop variable will likely be in its final (end of loop) state as it uses the value of the variable at the time the delete is executed, not created.
You need to make a local copy of the variable to get this to work:
int i = 0;
foreach(var tile in lib.dic.Values)
{
var tileForClosure = tile;
var iForClosure = i;
Button b = new Button( () = > { MainStatic.tile = tileForClosure ; } );
Checkbox c = new Checkbox( () = > { lib.arr[iForClosure].b = !lib.arr[iForClosure].b; } );
i++;
}
By creating a local copy on each loop the value does not change and so your delegate will use the value that you expect.

Lambda expression as ThreadStart strange behavior [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
C# - The foreach identifier and closures
From Eric Lippert’s blog: “don’t close over the loop variable”
I'm using a lambda expression as ThreadStart parameter, to run a method in a new thread using Thread class. This is my code:
delegate void del();
static void Do(int i)
{
Console.WriteLine(i);
}
static del CreateLoop(del Do)
{
return () =>
{
while (true)
{
Do();
Thread.Sleep(500);
}
};
}
static void Main(string[] args)
{
int n = 0;
var loop = CreateLoop(() => Do(n));
new Thread(() => loop()).Start();
Thread.Sleep(500);
n = 1;
}
And this is the output:
0
1
1
1
...
How is it possible?
Why if I change the value of my integer variable n, also changes the value of i (Do's parameter)?
You should make a different variable out of it, thus not changing the original value.
After all, all you're really doing is calling that same old 'function', the lambda expression passing the variable i over and over again, which indeed changes. It's nog like you're storing the initial value of the var i somewhere.
var loop = CreateLoop(() => Do(n));
This line is simply creating a new function and assigning it to a variable. This function, among other things, passes the value n to the Do function. But it's not calling the Do function, it's just creating a function which will, when executed, call the Do function.
You then start a new thread which calls the function, etc, but your new thread is still executing Do(n), passing the n variable to Do. That part doesn't change - you've created a function which references a particular place in memory (represented by the variable n) and continues to reference that place in memory even as you change the value which is stored there.
I believe the following would "fix" your code:
var loop = (int x) => () => CreateLoop(() => Do(x));
new Thread(loop(n)).Start();
This passes the value of n to the function represented by loop, but the loop function creates a new place in memory (represented by x) in which to store the value. This new place in memory is not affected by subsequent changes to n. That is to say, the function you've created does not directly reference the place in memory to which n is a pointer.

Categories

Resources