lambad表达式的概念
可以将lambad表达式 理解为匿名函数的简写
它除了写法不同外
使用上和匿名函数一模一样
都是和委托或者事件 配合使用的
lambad表达式语法
匿名函数
cs
delegate (参数列表)
{
};
lambad表达式
cs
(参数列表) =>
{
//函数体
};
使用
1.无参无返回
cs
Action a = () =>
{
Console.WriteLine("无参无返回值的lambad表达式");
};
a();
2.有参
cs
Action<int> a2 = (int value) =>
{
Console.WriteLine("有参数Lambad表达式{0}", value);
};
a2(100);
3.甚至参数类型都可以省略 参数类型和委托或事件容器一致
cs
Action<int> a3 = (value) =>
{
Console.WriteLine("省略参数类型的写法{0}", value);
};
a3(200);
4.有返回值
cs
Func<string, int> a4 = (value) =>
{
Console.WriteLine("有返回值有参数的那么大表达式{0}", value);
return 1;
};
闭包
当一个匿名函数(Lambda 表达式或匿名委托)引用了它外部作用域的变量时,编译器会自动将这些变量的生存期延长,使得它们不会因为原始作用域结束而被销毁,而是与委托实例一同存活。
内层的函数可以引用包含在它外层的函数的变量
即使外层函数的执行已经终止
注意:
该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
cs
class Test
{
public event Action action;
public Test()
{
int value = 10;
//这里就形成了闭包
//因为 当构造函数执行完毕时 其中申明的临时变量value的声明周期被改变了
action = () =>
{
Console.WriteLine(value);
};
for (int i = 0; i < 10; i++)
{
//此index 非彼index
int index = i;
action += () =>
{
Console.WriteLine(index);
};
}
}
public void DoSomthing()
{
action();
}
}
第一个闭包Value
value 是构造函数的局部变量。
普通情况下,构造函数执行完毕后,value 应该被销毁。
但是 action = () => Console.WriteLine(value); 这个 Lambda 使用了 value。
编译器会发现这个引用,于是在幕后做这件事:
1.生成一个隐藏类 (例如 c__DisplayClass0_0),里面有一个公共字段 value。
2.把 value 从栈上的局部变量提升到这个隐藏类的实例字段中。
3.把 Lambda 的方法体变成这个隐藏类的成员方法。
4.最后,action 委托引用了这个隐藏类实例的方法。
结果:value 不再随构造函数结束而消亡,而是和 action 委托一起存活 。即使 Test 对象创建完毕,value 依然被委托牢牢"抓住"。
第二个闭包:循环中的闭包 捕获 index
每次循环都创建一个新的局部变量 index ,并赋值为当前的 i。
每个 Lambda 捕获的是自己那个循环迭代中的 index。
编译器同样为每个 Lambda 生成一个隐藏类的实例(或复用同一个隐藏类但不同的字段,具体取决于编译器实现),每个实例持有各自的 index 副本。
因此,10 次循环会创建 10 个不同的捕获变量,互不干扰。
为什么不能直接捕获 i
闭包捕获的是变量容器 ,不是值。
for循环整个共用一个变量。
多个委托可以捕获同一个容器,从而相互影响。
要让每个委托独立,就要为每个委托创建独立的变量容器。
核心是 编译器会为每个不同的"捕获作用域"生成一个隐藏类,所有捕获相同外部变量的匿名函数可能会共享同一个隐藏类实例。
不同的捕获作用域会导致不同的隐藏类;同一个作用域内的多个变量会被合并到一个隐藏类中
value是一个作用域下,一个隐藏类,一个类实例。
index是一个作用域下,一个隐藏类,10个不同类实例。
要是一个作用域下,多个变量,也是一个类,类中存放多个字段保存变量。
作用域 指的是程序中变量可以被访问的代码区域。
一个变量从声明开始,到它所在的 { } 结束,这段范围就是它的作用域。方法体、循环体、if 块