0
点赞
收藏
分享

微信扫一扫

跨语言 闭包 C#深度解析


  • 闭包作为前端面试的必考题目
  • 但今天要说的是 主流语言都有闭包 如:C#、Java、C++、swift、Go,一些非主流语言也是有闭包的
  • 所以 闭包 并不是某个语言特有,闭包是跨越语言的设计
  • 本文代码以C#为主

得心应手

  • 一个闭包就是一个“捕获”了其生成的环境中、所引用的自由变量的函数
  • 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外

static void Closure()
{
var x = 1;
Action action= () =>
{
var y = 1;
var result = x + y;
Console.WriteLine(result);
x++;
};
action();
action();
}

// 输出:
2
3

  • 我们首先定义了一个委托action,它引用了“x”变量(x变量既不是入参,也不是委托内的局部变量), 这个变量将被action"捕获”,被自动添加到action 的运行环境
  • 当我们执行action时,原始的“x”已经脱离了它被引用时的作用域环境,但是两次执行能输出2,3 说明它脱离原引用环境仍然能用
  • 当你在代码调试器(debugger)里观察“action”时,可以看到C#编译器为我们创建了一个Target属性,里面封装了 x 变量:
  • 跨语言 闭包 C#深度解析_词法

源码追溯,委托继承自Delegate抽象类,Delegate类有个Target 属性(获取当前委托调用实例方法的实例类)
至此可以猜想: 我们每次执行委托,实际是是执行某个匿名类上的实例方法

刨根问底

  • 闭包是词法闭包的简称,维基百科上是这样定义的:
    在计算机编程中,闭包是在词法环境中绑定自由变量的头等函数

头等函数

头等函数( First Class)意味着语言将其视为第一类数据类型的函数, 意味着你可以将函数分配给一个变量(或作为参数传递),然后像正常函数一样调用

  • 很明显,C#常使用的委托(C#委托的演进:匿名函数–>lambda表达式)是头等函数

Func<string,string> myFunc = delegate(string var1)
{
return "some value";
};
Func<string,string> myFunc = var1 => "some value";
string myVar = myFunc("something");

自由变量

  • 自由变量是在匿名函数/lambda表达式中被引用的变量,它不是函数的参数也不是函数的局部变量

var myVar = "this is good";
Func<string,string> myFunc = delegate(string var1)
{
return var1 + myVar;
};

  • 词法作用域引用的自由变量,注意,引用自由变量,并不是使用当时自由变量的值

通俗点, 就是告知这个变量环境,我这个匿名函数等会执行时要用到这个变量;如果我没被销毁,你不能销毁我引用的自由变量

洗尽铅华

循环内开启的Task任务,并不保证执行顺序

Demo1

static void Closure1()
{
for (int i = 0; i < 5; i++)
{
Task.Run(()=> Console.WriteLine(i));
}
}
// 输出:
5
5
5
5
5

Demo2

static void Closure2()
{
for (int i = 0; i < 5; i++)
{
int j = i;
Task.Run(() => Console.WriteLine(j));
}
}
// 输出:
3
0
1
4
2
// 多次执行的结果不一样,但是总是会保持输出 0,1,2,3,4 的乱序组合

Demo1:输出5,5,5,5,5

为什么加上临时变量就能输出"预期"?

Demo2:输出乱序的0,1,2,3,4

  • 有这样的认知,理解JavaScript 闭包也就不难了
  • 有了上面的理解,下面这段 js 就很好解释了

跨语言 闭包 C#深度解析_匿名函数_04


闭包 的概念核心: 头等函数、自由变量


举报

相关推荐

0 条评论