Matias Cicero
Matias Cicero

Reputation: 26281

Different results between yield return and LINQ Select

I always thought that these two methods were similar:

public static IEnumerable<Func<int>> GetFunctions()
{
     for(int i = 1; i <= 10; i++)
         yield return new Func<int>(() => i);
}

public static IEnumerable<Func<int>> GetFunctionsLinq()
{
     return Enumerable.Range(1, 10).Select(i => new Func<int>(() => i));
}

Yet, they yield different results when converting them to a List<Func<int>>:

List<Func<int>> yieldList = GetFunctions().ToList();
List<Func<int>> linqList = GetFunctionsLinq().ToList();

foreach(var func in yieldList)
   Console.WriteLine("[YIELD] {0}", func());

Console.WriteLine("==================");

foreach(var func in linqList)
   Console.WriteLine("[LINQ] {0}", func());

The output is:

[YIELD] 11
[YIELD] 11
[YIELD] 11
[YIELD] 11
[YIELD] 11
[YIELD] 11
[YIELD] 11
[YIELD] 11
[YIELD] 11
[YIELD] 11
==================
[LINQ] 1
[LINQ] 2
[LINQ] 3
[LINQ] 4
[LINQ] 5
[LINQ] 6
[LINQ] 7
[LINQ] 8
[LINQ] 9
[LINQ] 10

Why is this?

Upvotes: 6

Views: 1998

Answers (2)

M.kazem Akhgary
M.kazem Akhgary

Reputation: 19149

That's closure problem. You have to store the variable inside the loop to solve this problem.

for (int i = 1; i <= 10; i++)
{
    var i1 = i;
    yield return new Func<int>(() => i1);
}

In fact new Func<int>(() => i); uses the exact value of counter inside loop and that's not a copy. So after the loop finishes you always get 11, because it was the last value set to counter.

Upvotes: 6

Jakub Lortz
Jakub Lortz

Reputation: 14896

The i in for(int i = 1; i <= 10; i++) is the same variable in each loop, just changing value.

() => i is a closure. When it is called, it uses the current value of i, not the value that i had when the Func was created.

You first enumerate GetFunctions before calling the returned functions, so i already is 11 in each of them.

If you call the functions immediately after getting them from the enumerator, you will get the same results as with the LINQ version:

foreach (var f in GetFunctions())
    Console.WriteLine("[YIELD2] {0}", f());

Anyway, it's not a good idea to create closures over loop variables.

Upvotes: 1

Related Questions