Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different results between yield return and LINQ Select

Tags:

c#

linq

delegates

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?

like image 536
Matias Cicero Avatar asked Nov 20 '15 19:11

Matias Cicero


People also ask

What does LINQ Select return?

By default, LINQ queries return a list of objects as an anonymous type. You can also specify that a query return a list of a specific type by using the Select clause.

Does LINQ use yield?

This keyword is used to return items from a loop within a method and retain the state of the method through multiple calls. Yield returns IEnumerator or generic IEnumerator<T>. This is very useful in LINQ query expressions in C# 3.0 as this provides the iterations required in LINQ queries.

What does LINQ return when the results are empty?

It will return an empty enumerable. It won't be null.

Does LINQ select return new object?

While the LINQ methods always return a new collection, they don't create a new set of objects: Both the input collection (customers, in my example) and the output collection (validCustomers, in my previous example) are just sets of pointers to the same objects.


2 Answers

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.

like image 192
M.kazem Akhgary Avatar answered Sep 21 '22 00:09

M.kazem Akhgary


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.

like image 31
Jakub Lortz Avatar answered Sep 20 '22 00:09

Jakub Lortz