Lambda表达式
Lambda表达式实际上是简化委托的写法,只要有委托参数类型的地方,就可以使用Lambda表达式表示。
// 委托写法
Action<int, int> act = Add;
public static void Add(int a,int b)
{
Console.WriteLine(a + b);
}
// Lambda表达式写法
Action<int, int> act = (a, b) => Console.WriteLine(a + b);
Lambda表达式原理
Lambda表达式是语法糖,根据是否使用类的成员可以分为几种情况讨论
使用类的成员
- 使用类的成员,就不会生成匿名类,而是生成一个内部方法
- 这个内部方法会有一个地址指向类的成员
引用方法内部的局部变量
这种情况下会生成一个匿名类。编译器在背后生成私有的密封类<>c,其中 <>9_5_0 定义委托静态变量,委托的逻辑写到了这个类的Addb_5_0函数里面。Lambda表达式所在类中所有非闭包的代码都生成到<>c类中。
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
……
public static Action<int, int> <>9__5_0;
internal void <.ctor>b__5_0(int a, int b)
{
Console.WriteLine(a + b);
}
……
}
private Action<int, int> act = <>c.<>9__5_0 ?? (<>c.<>9__5_0 = new Action<int, int>(<>c.<>9.<.ctor>b__5_0));
闭包
Lambda表达式可以在委托方法中,跨作用域访问类的成员、函数的局部变量、函数的入参。
class Test
{
int count = 0;
public void M()
{
List<Action> lists = new List<Action>();
for (int i = 0; i < 5; i++)
{
lists.Add(() => { Console.WriteLine($"{i}:{count++}"); });
}
foreach (var v in lists)
{
v();
}
}
}
在调用v()时,局部变量i本已经释放,但仍能打印出结果,于是可以说,闭包延长了变量的生命周期。
分析结果
原理如下:
很直观的看到使用for循环时,只创建了一个对象 <>c_DisplayClass1_0,循环使用的变量是 <>c_DisplayClass0.i,局部变量i同时被5个闭包引用,这5个闭包共享i,所以最后他们打印出来的值是一样的,都是i最后退出循环时候的值5。
public class Test
{
[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
public Test <>4__this; // 引用的是对象,而非成员变量
public int i;
internal void <M>b__0()
{
Test test = <>4__this;
int count = <>4__this.Count;
int i = this.i;
test.Count = count + 1;
Console.WriteLine($"{i}:{count}");
}
}
private int Count = 0;
private void M()
{
List<Action> lists = new List<Action>();
<>c__DisplayClass1_0 <>c__DisplayClass1_ = new <>c__DisplayClass1_0(); // 循环只定义一个对象
<>c__DisplayClass1_.<>4__this = this;
<>c__DisplayClass1_.i = 0;
while (<>c__DisplayClass1_.i < 5)
{
lists.Add(new Action(<>c__DisplayClass1_.<M>b__0));
<>c__DisplayClass1_.i++;
}
foreach (Action v in lists)
{
v();
}
}
}
如代码所示,在最后调用委托时,<>c_DisplayClass1_.i值已经是5了,而此时test.count的值还是0,每调用一次委托就会输出一次i的值5和count逐渐加1的值
【精选】C#:深入理解 lambda表达式与闭包_lambda表达式闭包-CSDN博客
工具:ILDasm.exe