閉包捕獲與lazy val欄位

幾天前有同事聊起這個話題:大家在review一段Scala代碼,裡面有一個lazy val欄位被用在一個閉包里,而這個閉包是要被序列化傳到別的機器上去執行的。話題的重點就是在閉包前這段代碼特意前訪問了一下這個lazy val欄位的值,引起同事討論是否有必要。

其中一位同事對Scala不熟而對微軟系技術更熟,於是我就把這個例子用C#來寫了一次給他看。結果他沒聽說過C#里有System.Lazy<T>…不過還好C#的Lazy<T>是標準庫層面的功能,畢竟magic少一些,還是比Scala的好解釋一些。

C#和Scala的代碼例子在這個gist里:demo C# and Scalas lazy val and lambda capturing

真正的重點是:

  • C#和Scala里如果「捕獲了欄位」的話,其實捕獲的是this而不是單獨捕獲了一個欄位,在lambda里訪問欄位其實是通過捕獲的this來訪問的。Java 8的lambda也是如此。

  • 對this的捕獲並不會導致lazy val被求值,所以就算lambda里會用到一個lazy val欄位,在閉包創建的時候並不會當時就導致該欄位求值。如果要包裝這個lazy val欄位在lambda表達式實際被調用時得到閉包創建時的一些環境狀態的話,得自己顯式在閉包創建前對這個lazy val欄位求一下值才行。

然後同事表示還是C++好,lambda的捕獲列表很明確哪些是capture-by-value哪些是capture-by-reference。然而其實來個 [&] 就可以把按引用捕獲this的事實給隱含起來了,對新手來說也沒友好多少吧(ry

#include <iostream>n#include <functional>nnclass Foo {n int val_ = 0;nnpublic:n std::function<int()> get_func() {n return [&]() { return val_; };n }nn void incr() { val_++; }n};nnint main() {n Foo foo;n const auto& f = foo.get_func();n std::cout << f() << std::endl;n foo.incr();n std::cout << f() << std::endl;n return 0;n}n

讓不熟悉現代C++的人來讀這段代碼,能看出來那個lambda隱式按引用捕獲了this么 >_<

推薦閱讀:

在C#的ArrayList中的對象可以是結構體嗎?如果可以,怎麼使用比較簡便?補充:結構體包含多個欄位。
Unity3D 5.3 新版AssetBundle使用方案及策略
從遊戲腳本語言說起,剖析Mono搭建的腳本基礎
Visual Studio 開發體驗究竟牛到什麼程度?真的只是拖拖控制項就能完成中小型項目開發?
【譯】介紹 .NET Standard

TAG:Scala | C# | 编程语言 |