C#学习笔记(三)—–C#高级特性:Lambda表达式

Lambda表达式

Lambda表达式是写在委托实例上的匿名方法。编译器立即将Lambda表达式转换成下面两种形式中的一种:
①委托实例
Expression<TDelegate>类型的表达式树,该表达式树将Lambda表达式内的代码显示为可遍历的对象模型。这使得对Lambda的解释可以延迟到运行时。
下面的委托类型:
delegate int Trasform (int i);
可以指定和调用下面的Lambda表达式:
Trasform sqr = x => x * x;
Console.WriteLine (sqr(3)); // 9

提示:编译器在内部将Lambda表达式编译成一个私有方法,并把表达式代码移到方法中:①首先Trasform委托被编译为下面的形式:
委托的形式
②lambda方法被编译成一个私有的静态方法:
这里写图片描述

  • lambda表达式有以下形式:(参数)=>表达式或语句块
    为了方便,在只有一个可推测类型的参数时,可以省略小括号。
  • lambda表达式使每一个参数的类型和委托的参数的类型一致,使表达式的返回类型和委托的返回类型一致。
  • lambda表达式代码可以是表达式也可以是语句块:在上例中,我们也可以这样写:
Trasform sqr = x =>{ x * x;};
  • lambda表达式通常可Func或Action一起使用,上例也可以这样写:
Func<int,int> func=x =>{ x * x;};
  • 下面是一个带两个参数的lambda表达式:
Func<string,string,int> totalLength = (s1, s2) => s1.Length + s2.Length;
int total = totalLength ("hello", "world"); // total is 10;
  • Lambda表达式是C#3.0引入的概念。
  • 明确指定lambda表达式的参数类型:lambda表达式可以根据代码的上下文推断出参数类型,当不能推断出时,可以指定lambda的参数类型。考虑下面的代码:
Func<int,int> sqr = x => x * x;

编译器可以根据类型推断来判断x的类型是一个int。
我们可以上例中显示的为x指定一个类型:

Func<int,int> sqr = (int x) => x * x;
  • 捕获外部变量:Lambda表达式可以引用局部变量以及定义该lambda表达式的方法的参数:
static void Main()
{
int factor = 2;
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3)); // 6
}
  • 上例中的factor对于lambda表达式来说,叫做外部变量(outer variable),lambda引用外部变量的过程叫做捕获外部变量。lambda捕获外部变量就叫做闭包(closure)。
  • 捕获的变量在真正调用委托时赋值,而不是在捕获的时候赋值:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
  • lambda表达式可以自动更新被捕获的变量:
int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
Console.WriteLine (seed); // 2
  • 被捕获的变量的生命周期可以延长至捕获它的委托的生命周期(至少),在下面的例子中,局部变量seed本应在Natural()执行完后就消失了,但是由于seed被lambda表达式捕获了,所以它的生命周期被延长了:
static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
}

在lambda表达式内捕获已经实例化过的seed,在每次调用委托实例时都是唯一的,如果把上例中的seed的实例化过程放到lambda表达式内,则产生的结果不同:

static Func<int> Natural()
{
return() => { int seed = 0; return seed++; };
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 0
}

提示:捕获局部变量的过程在内部的实施过程是这样的:创建一个私有类,并将被捕获的局部变量提升为这个私有类的字段(由局部变量级别提升至字段的级别)。这样,当方法被调用时,这个私有类被实例化,并将其生命周期绑定到委托的实例上。

  • 捕获循环变量:当捕获到的是一个for循环的循环变量,C#把这个循环变量当作是在for循环外部定义的。这意味着lambda在每次迭代中得到的循环变量的值都是一样的。
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
actions [i] = () => Console.Write (i);
foreach (Action a in actions) a(); // 333

上述生成的结果是333而不是012,原因是这样的:
①还是上面讲过的,捕获的变量是在调用委托时赋值,而不是在捕获的时候赋值,这意味着在调用action a in actions这句的时候捕获的变量才被赋值,而循环变量这个时候已经被迭代到了3。
②lambda表达式会自动更新值,这意味着lambda表达式总是会寻找到循环变量的最新值。循环变量的最终的最新的值就是3。
把for循环展开的话更容易理解:

Action[] actions = new Action[3];
int i = 0;
actions[0] = () => Console.Write (i);
i = 1;
actions[1] = () => Console.Write (i);
i = 2;
actions[2] = () => Console.Write (i);
i = 3;
foreach (Action a in actions) a(); // 333

解决的方法是,把每次循环的变量都赋值非一个临时的变量,这个临时的变量放在lambda表达式的内部:

Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
int loopScopedi = i;
actions [i] = () => Console.Write (loopScopedi);
}
foreach (Action a in actions) a(); // 012

这样,就可使闭包在每次循环迭代的时候捕获一个不一样的变量。
- 在c#5.0之前,foreach循环和for循环的工作原理是一样的。

Action[] actions = new Action[3];
int i = 0;
foreach (char c in "abc")
actions [i++] = () => Console.Write (c);
foreach (Action a in actions) a(); // ccc in C# 4.0

这会引起一些困惑,不同于for循环,foreach循环中的循环变量在每一次循环迭代中都是不可变得,人们期望的是可以将它当作是循环体中的局部变量,好消息是这个情况在C#5.0中被修改了,上例中在C#5.0中运行的结果是abc。
需要注意的是,这在技术上是一种毁灭性的改变:因为在c#5.0和C#4.0的运行结果完全不一样,之前在C#4.0及以前版本写的代码在C#5.0上面的运行结果完全不同!也可以看到,之所以做这样的修改,很明显这就是C#4.0的一个bug了。(要不然也不会做这样的修改)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值