什么是lambda表达式

  • Lambda表达式其实就是一个用来代替委托实例未命名的方法;
  • 编译器会把lambda表达式转化为以下二者之一:
    • 一个委托实例
    • 一个表达式树(expression tree),类型是Expression,它表示了可遍历的对象模型中Lambda表达式里面的代码。它允许lambda表达式延迟到运行时再被解释。

例子

未命名图片.png
实际上,编译器会通过编写一个私有方法来解析这个lambda表达式,然后把表达式的代码移动到这个方法里。

Lambda表达式的形式

  • (parameters) => expression-or-statement-block
    • (参数) => 表达式或语句块
  • 其中如果只有一个参数并且类型可推断的话,那么参数的小括号可以省略。

Lambda表达式与委托

每个lambda表达式的参数对应委托的参数
表达式的类型对应委托的返回类型

  1. x => x * x;
  2. delegate int Transformer(int i);

Lambda表达式的代码也可以是语句块

  1. x => { return x * x; };

Func和Action

Lambda表达式通常与Func和Action委托一起使用
未命名图片.png

显式指定lambda表达式的参数类型
lambda表达式有参数,参数类型推断不出来
未命名图片.png
未命名图片.png

捕获外部变量

lambda表达式可以引用本地的变量和所在方法的参数

  1. static void Main(string[] args)
  2. {
  3. int factor = 2;
  4. Func<int, int> multiplier = n => n * factor;
  5. Console.WriteLine(multiplier(3));
  6. }

被捕获的变量

被lambda表达式引用的外部变量叫做被捕获的变量(captured variables)。
被捕获了外部变量的lambda表达式叫做闭包 closure
被捕获的变量是在委托被实际调用的时候才被计算,而不是在捕获的时候。

  1. int factor = 2;
  2. Func<int, int> multiplier = n => n * factor;
  3. factor = 10;
  4. Console.WriteLine(multiplier(3)); // 30

lambda表达式本身也可以更新被捕获的变量

  1. int seed = 0;
  2. Func<int> natural = () => seed++;
  3. Console.WriteLine(natural()); // 0
  4. Console.WriteLine(natural()); // 1
  5. Console.WriteLine(seed); // 2

被捕获的变量的声明周期会被延长和委托一样
未命名图片.png
在lamdba表达式内实例化的本地变量对于委托实例的每次调用来说都是唯一的。
未命名图片.png

捕获迭代变量

当捕获for循环的迭代变量时,C#会把这个变量当做是在循环体外部定义的变量,这就意味着每次迭代捕获的都是同一个变量。
未命名图片.png
未命名图片.png

如何解决

未命名图片.png

注意:foreach

C#4,和C#5+的区别
未命名图片.png

Lambda表达式 vs 本地方法

本地方法是C#7的一个新特性。它和lambda表达式在功能上有很多重复之处,但它有三个优点:
可以简单明了的进行递归
无需指定委托类型(那一堆代码)
性能开销略低一点
本地方法效率更高是因为它避免了委托的间接调用(需要CPU周期,内存分配)。本地方法也可以访问所在方法的本地变量,而且无需编译器把被捕获的变量hoist到隐藏的类。

匿名方法 vs Lambda表达式

匿名方法和Lambda表达式很像,但是缺少以下三个特性:
隐式类型参数 (x=>x*x)
表达式语法(只能是语句块) (()=>)
编译表达式树的能力,通过赋值给Expression

例子
未命名图片.png

匿名方法 - 其它

捕获外部变量的规则和Lambda表达式是一样的。

public event EventHandler Clicked =delegate{};

但匿名方法可以完全省略参数声明,尽管委托需要参数
这就便面了触发事件前的null检查

// 避免null检查
Clicked += delegate { Console.WriteLine("clicked"); };