程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C# 閉包問題-你被”坑“過嗎?,

C# 閉包問題-你被”坑“過嗎?,

編輯:C#入門知識

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的修改,期望博主修改,)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved