本文的知识点其实由golang知名的for循环陷阱发散而来,
对应到我的主力语言C#, 其实牵涉到闭包、foreach。为了便于理解,我重新组织了语言,以倒叙结构行文。
先给大家提炼出一个C#题:观察for、foreach闭包的差异
左边输出 5个5; 右边输出0,1,2,3,4, 答对的可以不用看下文了。
闭包是在词法环境中捕获自由变量的头等函数, 题中关键是捕获的自由变量。
demo1
- for循环内闭包,局部变量i是被头等函数引用的自由变量;相对于每个头等函数,i是全局变量;
- 闭包捕获变量i的时空和 闭包执行的时空不是一个时空;
- 所有闭包执行时,捕获的都是变量i,所以执行输出的都是
i++
最后的5。
这也是C#闭包的陷阱, 通常应对方式是循环内使用一个局部变量解构每个闭包与(相对全局)变量i的关系。
var t1 = new List<Action>();
for (int i = 0; i < 5; i++)
{
// 使用局部变量解绑闭包与全局自由变量i的关系,现在自由变量是局部变量j了。
var j = i;
var func = (() =>
{
Console.WriteLine(j);
});
t1.Add(func);
}
foreach (var item in t1)
{
item();
}
demo2
foreach内闭包,为什么能输出预期的0,1,2,3,4。
聪明的读者可以猜想,是不是foreach在循环迭代时 ,给我们搞出了局部变量j,帮我们解构了闭包与全局自由变量i多对1的关系。
foreach的底层实现有赖于IEnumerable
和IEnumerator
两个接口的实现、
但是怎么用这个两个接口,还需要看foreach伪代码:
C# foreach foreach (V v in x) «embedded_statement»
被翻译成下面代码。
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current; // 注意这句, 变量v的定义是在循环体内
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
请注意注释,变量v的定义是在循环内部, 因此使用foreach迭代时,每个闭包捕获的都是局部的自由变量, 因此foreach闭包执行时输出0,1,2,3,4。
如果变量V v定义在while语言上方,那么效果就和for循环一样了。
这是for循环/foreach迭代一个很有意思的差异。
本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/293372.html