我一直认为这两种方法是相似的:
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));
}
然而,将它们转换为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());
输出结果为:
[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
为什么会这样?
答案 0 :(得分:6)
那个关闭问题。您必须将变量存储在循环内以解决此问题。
for (int i = 1; i <= 10; i++)
{
var i1 = i;
yield return new Func<int>(() => i1);
}
实际上new Func<int>(() => i);
使用了计数器内部循环的确切值,并且它不是副本。因此,在循环结束后,您总是得到11
,因为它是设置为计数器的最后一个值。
答案 1 :(得分:1)
i
中的for(int i = 1; i <= 10; i++)
是每个循环中的相同变量,只是更改值。
() => i
是一个闭包。调用它时,它使用当前值i
,而不是i
创建Func
时的值。
首先在调用返回的函数之前枚举GetFunctions
,因此每个i
已经是11。
如果从枚举器中获取函数后立即调用它们,您将获得与LINQ版本相同的结果:
foreach (var f in GetFunctions())
Console.WriteLine("[YIELD2] {0}", f());
无论如何,在循环变量上创建闭包不是一个好主意。