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表达式来促成闭包。

 

再来看3个总结的例子:

 

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
在那上面处理都是同1的。但随着人们在采取进度中的各类不适,微软做出了”一点“妥协,在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”循环未有做修改。

总结

匿名函数和拉姆da表明式给我们的编制程序带来了重重急忙简单的贯彻,如(List.马克斯((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#伍的修改,期望博主修改,)

相关文章