yakya
yakya

Reputation: 5210

LINQ over local variables

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

Answers (3)

Sergey Berezovskiy
Sergey Berezovskiy

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

DuckMaestro
DuckMaestro

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

AliRıza Adıyahşi
AliRıza Adıyahşi

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

Related Questions