Reputation: 5210
This might seems trivial for some of you but I'm confused about these two examples below.
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int i = 0;
var simpleQuery =
from num in numbers
select ++i;
foreach (var item in simpleQuery)
{
Console.WriteLine("v = {0}, i = {1}", item, i); // now i is incremented
}
Outputs:
v = 1, i = 1
v = 2, i = 2
v = 3, i = 3
v = 4, i = 4
v = 5, i = 5
v = 6, i = 6
v = 7, i = 7
v = 8, i = 8
v = 9, i = 9
v = 10, i = 10
It updates the value of i and everything is fine up to this point. But when I tried to update the elements of array, it just doesn't work.
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var simpleQuery =
from num in numbers
select ++num;
int i = 0;
foreach (var item in simpleQuery)
{
Console.WriteLine("v = {0}, num = {1}", item, numbers[i++]); // now num is NOT incremented???
}
Outputs:
v = 6, num = 5
v = 5, num = 4
v = 2, num = 1
v = 4, num = 3
v = 10, num = 9
v = 9, num = 8
v = 7, num = 6
v = 8, num = 7
v = 3, num = 2
v = 1, num = 0
What might be the reason behind this?
Edit: I thought the second example will output:
v = 6, num = 6
v = 5, num = 5
v = 2, num = 2
v = 4, num = 4
v = 10, num = 10
v = 9, num = 9
v = 7, num = 7
v = 8, num = 8
v = 3, num = 3
v = 1, num = 1
Upvotes: 2
Views: 325
Reputation: 236218
Your query actually looks like
numbers.Select(num => ++num)
Which is actually call to extension method
Enumerable.Select(numbers, new Func<int, int>(num => ++num))
This method executes selector on each item of array. Selector is an anonymous function (i.e. name for this function will be generated by compiler). And each item is passed to that function. And here is reason why items in array stay unchanged - integer is a value type. Value types are passed by value (i.e. copy of item is created, instead of passing reference to item). So, inside selector copy is modified and returned. This does not affect original item in array.
In first case you have captured reference to i
variable in delegate, thats why i
is changed:
Enumerable.Select(numbers, new Func<int, int>(num => ++i))
Array items are still passed here as method argument, but i
is captured in method body. Actually such variables (which are part of method body) are replaced by compiler - in your first case variable i
is moved to fields of class, and all calls to that variable are replaced with calls to class field. I.e. in first case compiled code will look like:
Foo foo = new Foo();
foreach(int num in numbers)
Console.WriteLine(foo.Bar(num));
Where Foo
is a generated nested class, and Bar
is a selector delegate, which is a method, generated in that class.
private class Foo
{
private int _i; // variable is captured by delegate
public int Bar(int x)
{
_i = _i + 1; // thats why it has new value on next call
return _i;
}
}
Upvotes: 3
Reputation: 15885
Your first query is operating on a reference to i
(captured as part of a closure), while your second query is operating on copies of each array element, not references. The expressions passed to LINQ queries map to lambda expressions, and the variables inputted to a lambda expression (such as num
in your example) are copies for values types, not references.
Upvotes: 1
Reputation: 15866
var simpleQuery =
from num in numbers
select ++i;
means:
for(int i=0; i<numbers.Length; i++)
{
simpleQuery.Add(++i);
}
And
var simpleQuery =
from num in numbers
select ++num;
means:
for(int i=0; i<numbers.Length; i++)
{
simpleQuery.Add(numbers[i] + 1);
}
Upvotes: 0