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?
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.
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.
It will return an empty enumerable. It won't be null.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With