C#的"閉包"

最近在學習golang的時候發現一件有趣的事情,go有一個閉包的概念,因而我對比了一下C#的"閉包"...golang

  • golang的閉包

calc 是一個接收一個形參a、兩個函數返回值的函數,兩個函數返回值是指這一段代碼:(func(int) int, func(int) int),go語言支持多個返回值
main 函數,全部語言同樣程序的入口
【1】 經過main函數調用calc函數執行返回兩個函數,分別是f1和f2,同時會打印一邊參數a的內存地址
【2】 調用f1函數和f2函數,打印一遍add和sub裏面"引用"calc函數參數a的內存地址,並返回calc參數a+=i和a-=i的結果
【3】 輸出f1函數和f2函數執行返回的結果 安全

 1 func calc(a int) (func(int) int, func(int) int) {
 2     //打印變量地址
 3     fmt.Println(&a)
 4 
 5     //建立a+=add函數入參i,並返回的a的結果的函數,並返回給調用者
 6     add := func(i int) int {
 7         //返回值函數內部打印一遍變量地址,測試這裏面的a和calc的a內存地址是否一致
 8         fmt.Println(&a)
 9         a += i
10         return a
11     }
12     //建立a-=sub函數入參i,並返回的a的結果的函數,並返回給調用者
13     sub := func(i int) int {
14         fmt.Println(&a)
15         a -= i
16         return a
17     }
18     //返回兩個函數
19     return add, sub
20 }
21 
22 func main() {
23     //f1對應add f2對應sub
24     f1, f2 := calc(10)
25     //調用f1和f2函數,同時打印f1 f2返回結果
26     fmt.Println(f1(1), f2(2)) //11 9
27     fmt.Println(f1(3), f2(4)) //12 8
28     fmt.Println(f1(5), f2(6)) //13 7
29 }

 打印結果:閉包

0xc00000a0a8      調用calc函數所打印 fmt.Println(&a) 的結果函數

0xc00000a0a8      調用f1(1) add裏面的 fmt.Println(&a) 
0xc00000a0a8      調用f2(2) sub裏面的 fmt.Println(&a) 
11 9           f1(1)和f2(2) 返回值學習

0xc00000a0a8      調用f1(3) 
0xc00000a0a8      調用f1(4) 
12 8           f1(3)和f2(4) 返回值測試

0xc00000a0a8      調用f1(5) 
0xc00000a0a8      調用f1(6) 
13 7           f1(5)和f2(6) 返回值this

是否是很驚訝,calc函數參數a這個值變量變成了引用關係,在calc函數執行完以後並無釋放,並供給add和sub使用,這就是go的閉包。spa

 

  • C#的"閉包"

 經過下面代碼能「實現」上面golang的閉包指針

 1 public void Calc(int a, out Func<int, int> add, out Func<int, int> sub)
 2 {
 3     add = (int i) =>
 4     {
 5         return a += i;
 6     };
 7 
 8     sub = (int i) =>
 9     {
10         return a -= i;
11     };
12 }
13 
14 public void Show()
15 {
16     Func<int, int> f1;
17     Func<int, int> f2;
18     this.Calc(10, out f1, out f2);
19 
20     Console.WriteLine(string.Format("{0}  {1}", f1(1), f2(2)));
21     Console.WriteLine(string.Format("{0}  {1}", f1(3), f2(4)));
22     Console.WriteLine(string.Format("{0}  {1}", f1(5), f2(6)));
23 }

 返回值打印結果:調試

11 9
12 8
13 7

得出結論確實是能實現和golang上面代碼同樣的功能,calc函數參數a變量變成"引用"關係,提供給add和sub使用。

可是...我將代碼改了下

 1 public class TestFunc
 2 {
 3     //存儲內存地址的字典,供後續查看
 4     private unsafe Dictionary<string, int*[]> PADic = new Dictionary<string, int*[]>();
 5 
 6     public void Calc(int a, out Func<int, int> add, out Func<int, int> sub)
 7     {
 8         //運行執行不安全的代碼
 9         unsafe
10         {
11             //取a的內存地址
12             int* aa = (int*)a;
13             //將內存地址存儲到字典中
14             this.PADic.Add("Calc(a):", new int*[] { aa });
15         }
16 
17         add = (int i) =>
18         {
19             unsafe
20             {
21                 int* aa = (int*)a;
22                 this.PADic.Add(string.Format("f1({0}):", i), new int*[] { aa });
23             }
24             return a += i;
25         };
26 
27         sub = (int i) =>
28         {
29             unsafe
30             {
31                 int* aa = (int*)a;
32                 this.PADic.Add(string.Format("f2({0}):", i), new int*[] { aa });
33             }
34             return a -= i;
35         };
36     }
37 
38     public void Show()
39     {
40         Func<int, int> f1;
41         Func<int, int> f2;
42         this.Calc(10, out f1, out f2);
43         Console.WriteLine(string.Format("{0}  {1}", f1(1), f2(2))); // 11 9
44         Console.WriteLine(string.Format("{0}  {1}", f1(3), f2(4))); // 12 8
45         Console.WriteLine(string.Format("{0}  {1}", f1(5), f2(6))); // 13 7
46     }
47 }

 (指針地址沒辦法輸出,直接調試看變量內容)輸出結果:

 

經過上面代碼能夠看出,每次調用f1和f2的時候,所"引用"的參數a是「值複製」的,而不是引用,由於它們的內存地址是有變化。

得出結論,功能雖然都能實現,可是有本質區別的。

相關文章
相關標籤/搜索