提问人:Ziv 提问时间:12/6/2021 更新时间:12/6/2021 访问量:652
Closure 和 Linq [复制]
Closure and Linq [duplicate]
问:
IEnumerable<char> charQuery1 = "Not what you might expect";
string vowels = "aeiou";
for (int i = 0; i < vowels.Length; i++)
charQuery1 = charQuery1.Where(c => c != vowels[i]);
foreach (char c in charQuery1) Console.Write(c);
这将引发 IndexOutofRange 异常。编译器将 for 循环变量“i”的作用域定为外部声明的变量。有人可以剖析一下,让我更舒服地理解吗?调试在这里对我没有多大帮助。我在这里试图理解编译器的角色。
答:
当您在代码的最后一部分枚举 charQuery1 时,您传入的谓词(即 )将以延迟方式执行 ()。到那一刻,变量的值是循环中的最后一个值,即“5”。因此,您捕获了从内部闭包修改的值。
您必须记住,LINQ 表达式具有延迟执行,并且在声明它们时执行,而是在枚举它们时执行。Where
c != vowels[i]
foreach (char c in charQuery1)
i
for (int i = 0; i < vowels.Length; i++)
为了避免这个问题,你应该像这样将循环索引捕获到循环作用域内的临时变量中。
IEnumerable<char> charQuery1 = "Not what you might expect";
string vowels = "aeiou";
for (int i = 0; i < vowels.Length; i++)
{
int index = i; // Temporary index variable. Should be declared inside the scope!
charQuery1 = charQuery1.Where(c => c != vowels[index]);
}
foreach (char c in charQuery1) Console.Write(c);
UPD:完全延迟行为:
IEnumerable<char> charQuery1 = "Not what you might expect";
string vowels = "aeiou";
Enumerable
.Range(0, vowels.Length)
.ForEach(i =>
{
charQuery1 = charQuery1.Where(c => c != vowels[i]);
});
foreach (char c in charQuery1) Console.Write(c);
但进一步简化会给你这个:
IEnumerable<char> charQuery1 = "Not what you might expect";
string vowels = "aeiou";
charQuery1 = Enumerable
.Range(0, vowels.Length)
.Aggregate(charQuery1, (cq, i) => cq.Where(c => c != vowels[i]));
foreach (char c in charQuery1) Console.Write(c);
很酷,不是吗?
评论
for
Enumerable .Range(0, vowels.Length) .ForEach(i => { charQuery1 = charQuery1.Where(c => c != vowels[i]); });
Linq 函数,例如创建将延迟计算的 IEnumerable。Where
这意味着在调用函数时不会生成函数的结果。在迭代返回的 IEnumerable by 时,将生成函数的结果。Where
Where
Where
Where
在示例代码中,函数的结果在代码末尾的 foreach 循环中循环迭代:Where
foreach (char c in charQuery1) Console.Write(c);
这是函数返回的枚举被迭代的地方,导致执行提供给函数的谓词委托。Where
Where
在您的例子中,这些谓词委托是 形式的匿名函数,它们在变量 上闭合。c => c != vowels[i]
i
同样,仅当执行代码末尾的 foreach 循环时,才会调用这些匿名函数。c => c != vowels[i]
那么,执行 foreach 循环时的价值是什么?这是循环退出时的最后一个值。换言之,的值等于 。i
for
i
vowels.Length
这导致谓词委托等价于 。显然,将导致 IndexOutOfRangeException。c => c != vowels[vowels.Length]
vowels[vowels.Length]
如何解决?
幼稚的方法是不要让谓词在 for 循环的计数器变量上闭合。相反,让它关闭一个变量,该变量的作用域为 for 正文:i
for (int i = 0; i < vowels.Length; i++)
{
var local_i = i;
charQuery1 = charQuery1.Where(c => c != vowels[local_i]);
}
但正如 Tim Schmelter 所指出的,更优雅、更简短的解决方案是简单地使用 Linq 的方法:Except
var charQueryWithoutVowels = charQuery1.Except(vowels);
评论
charQuery1 = charQuery1.Except(vowels)
i