C# 闭包问题-你被”坑“过吗?

引言

闭包是什么?以前看面试题的时候才发现这个名词。

闭包在实际项目中会有什么问题?现在就让我们一起来看下这个不太熟悉的名词。

如果在实际工作中用到了匿名函数和lamada表达式,那你就应该高度注意啦.

问题

请问下大家这段代码的输出结果是什么样的呢?

public static void Main()
{
Console.WriteLine("Starting."); for (int i = 0; i < 4; ++i)
Task.Run(() => Console.WriteLine(i)); Console.WriteLine("Finished. Press <ENTER> to exit.");
Console.ReadLine();
}

输出结果:

Starting.
Finished. Press <ENTER> to exit.
4
4
4
4

你答对了吗?如果没有请跟随我一起来看下这里的深层原因。

问题解决

public static void Main()
{
Console.WriteLine("Starting."); for (int i = 0; i < 4; ++i)
{
int j = i;
Task.Run(() => Console.WriteLine(j));
} Console.WriteLine("Finished. Press <ENTER> to exit.");
Console.ReadLine();
}

输出结果

Starting.
Finished. Press <ENTER> to exit.
0
1
3
2

原因分析

闭包是什么?

using System;

class Test
{
static void Main()
{
Action action = CreateAction();
action();
action();
} static Action CreateAction()
{
int counter = 0;
return delegate
{
// Yes, it could be done in one statement;
// but it is clearer like this.
counter++;
Console.WriteLine("counter={0}", counter);
};
}
}

输出

counter=1
counter=2

In essence, a closure is a block of code which can be executed at a later time, but which maintains the environment in which it was first created - i.e. it can still use the local variables etc of the method which created it, even after that method has finished executing.

这段话的大意是:从本质上说,闭包是一段可以在晚些时候执行的代码块,但是这段代码块依然维护着它第一个被创建时环境(执行上下文)- 即它仍可以使用创建它的方法中局部变量,即使那个方法已经执行完了。

这段话准确地来说不能算作定义,但形象的给出了描述。这里就不给出绝对定义啦。wiki上有这方面的描述。

C#中通常通过匿名函数和lamada表达式来实现闭包。

再来看一个简单的例子:

var values = new List<int> { 100, 110, 120 };
var funcs = new List<Func<int>>(); foreach (var v in values)
funcs.Add(() =>
{
//Console.WriteLine(v);
return v;
}); foreach (var f in funcs)
Console.WriteLine(f()); Console.WriteLine("{0}{0}", Environment.NewLine); funcs.Clear();
for (var i = 0; i < values.Count; i++)
{ //var v2 = values[i];
   funcs.Add(() =>
{ var v2 = values[i]; //will throw exception
        return v2;
});
} foreach (var f in funcs)
Console.WriteLine(f());

一语道破天机

Because ()=>v means "return the current value of variable v", not "return the value v was back when the delegate was created",Closures close over variables, not over values.

原文大意:因为() = > v "返回变量 v 的当前值",而不是创建该委托时"v“ 的返回值 。闭包”变量“,而不是闭包”值“。

所以在”for“循环中的添加的匿名函数,只是返回了变量i 而不是i的值。所以知道f() 被真正执行时,i已经是values.Count 值啦,所以会抛出”超出索引范围“。那为啥foreach 没事呢?那就让我们接着看下闭包的来头。

历史简述

The latter. The C# 1.0 specification actually did not say whether the loop variable was inside or outside the loop body, as it made no observable difference. When closure semantics were introduced in C# 2.0, the choice was made to put the loop variable outside the loop, consistent with the "for" loop.--Eric Lippert

闭包在C#2.0 的时候引入了闭包语法,选择将循环变量放在循环体外面,for 和foreach 在这方面处理都是一致的。但随着人们在使用过程中的种种不适,微软做出了”一点“让步,在C#5 中对”foreach“做了调整,但对”for“没有做改动。具体改动如下说:

We are taking the breaking change. In C# 5, the loop variable of a foreach will be logically inside the loop, and therefore closures will close over a fresh copy of the variable each time. The "for" loop will not be changed. --Eric Lippert

原文大意:在C#5中我们做了巨大的调整,“foreach”的遍历中的定义的临时循环变量会被逻辑上限制在循环内,“foreach”的每次循环都会是循环变量的一个拷贝,这样闭包就看起来关闭了(没有了)。但“for”循环没有做修改。

总结

匿名函数和Lambda表达式给我们的编程带来了许多快捷简单的实现,如(List.Max((a)=>a.Level)等写法)。但是我们要清醒的意识到这两个糖果后面还是有个”坑“(闭包)。这再次告诉我们技术工作人,要”知其然,也要知其所以然“。

参考文献

Closing over the loop variable considered harmful

Closing over the loop variable, part two

For Loop result in Overflow with Task.Run or Task.Start

Is there a reason for C#'s reuse of the variable in a foreach?

The Beauty of Closures

《代码的未来》读书笔记:也谈闭包(介绍较全面,但需要更新C#5的修改,期望博主修改,)

作者:旭东

C# 闭包问题的更多相关文章

  1. 《Web 前端面试指南》1、JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  2. 干货分享:让你分分钟学会 JS 闭包

    闭包,是 Javascript 比较重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,很难从定义去理解它.因此,本文不会对闭包的概念进行大篇幅描述 ...

  3. 深入浅出JavaScript之闭包(Closure)

    闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面写下我的学习笔记~ 闭包-无处不 ...

  4. javascript之闭包理解以及应用场景

    半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...

  5. js闭包 和 prototype

    function test(){ var p=200; function q(){ return p++; } return q; } var s = test(); alert(s()); aler ...

  6. js闭包for循环总是只执行最后一个值得解决方法

    <style> li{ list-style: none;width:40px;height: 40px;text-align:center;line-height: 40px;curso ...

  7. JavaScript学习笔记(二)——闭包、IIFE、apply、函数与对象

    一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...

  8. 带你一分钟理解闭包--js面向对象编程

    上一篇<简单粗暴地理解js原型链--js面向对象编程>没想到能攒到这么多赞,实属意外.分享是个好事情,尤其是分享自己的学习感悟.所以网上关于原型链.闭包.作用域等文章多如牛毛,很多文章写得 ...

  9. 如何设计一门语言(七)——闭包、lambda和interface

    人们都很喜欢讨论闭包这个概念.其实这个概念对于写代码来讲一点用都没有,写代码只需要掌握好lambda表达式和class+interface的语义就行了.基本上只有在写编译器和虚拟机的时候才需要管什么是 ...

  10. JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

随机推荐

  1. HashMap源码解读(转)

    http://www.360doc.com/content/10/1214/22/573136_78188909.shtml 最近朋友推荐的一个很好的工作,又是面了2轮没通过,已经是好几次朋友内推没过 ...

  2. SWT的对话框们

    对话框,都继承自org.eclipse.swt.widgets.Dialog,有Modal的和Modeless的区分,一般的对话框处理程序如下: <DialogType> dlg = ne ...

  3. Android网络服务发现(NSD)协议的使用

    Android的网络服务发现协议(NSD)能够用于在小范围的网络中发现邻近设备上的某个应用.这对于一些社交网络.多人游戏类的应用会很有帮助. Android的NSD的用法大致上分为四种操作: 1. 注 ...

  4. Socket编程实践(12) --UDP编程基础

    UDP特点 无连接,面向数据报(基于消息,不会粘包)的传输数据服务; 不可靠(可能会丢包, 乱序, 反复), 但因此普通情况下UDP更加高效; UDP客户/服务器模型 UDP-API使用 #inclu ...

  5. dede 首页或列表页调用文章内容页body内容

    在使用dede过程,有的朋友会调调出文章的列表的内容出来,怎么调呢?当然是用dede的传参的数据查询语句了,方法如下: {dede:arclist flag=h typeid=2 row=1 titl ...

  6. bat文件无法双击运行

    问题: win7系统下新建txt文件,编辑脚本内容后,保存为test.bat.每次双击它,只会默认以txt格式打开它,而不是运行它. 解决: 1. 双击打开“我的电脑”,然后在“工具”下选择“文件夹选 ...

  7. Javascript语言精粹之String常用方法分析

    Javascript语言精粹之String常用方法分析 1. String常用方法分析 1.1 String.prototype.slice() slice(start,end)方法复制string的 ...

  8. HPUX平台经常使用命令列举

    原创作品,出自 "深蓝的blog" 博客,欢迎转载,转载时请务必注明出处,否则追究版权法律责任. 深蓝的blog:http://blog.csdn.net/huangyanlong ...

  9. C++程序代写实现HashSet class

    C++程序代写实现HashSet class 专业程序代写(QQ:928900200) Implement a HashSet class for elements of type string.It ...

  10. Android 4.0新组件:GridLayout详细说明

    于Android 4.0(API 14)它提供了一个新的组件GridLayout,它继承自Linearlayout,用于执行网络格样式布局. 在某些方面,GridLayout与TableLayout和 ...