C#的"闭包"

最近在学习golang的时候发现一件有趣的事情,go有一个闭包的概念,于是我对比了一下C#的"闭包"...

  • 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) 返回值

是不是很惊讶,calc函数参数a这个值变量变成了引用关系,在calc函数执行完之后并没有释放,并供给add和sub使用,这就是go的闭包。

 

  • 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 }

 (指针地址没办法输出,直接调试看变量内容)输出结果:

C#的"闭包"

 

通过上面代码可以看出,每次调用f1和f2的时候,所"引用"的参数a是“值复制”的,而不是引用,因为它们的内存地址是有变化。

得出结论,功能虽然都能实现,但是有本质区别的。

 

上一篇:2021-04-24


下一篇:mysql备份数据库的命令