快乐的Lambda表达式(一)
转载:http://www.cnblogs.com/jesse2013/p/happylambda.html
原文出处: Florian
Rappl 译文出处:Jesse
Liu
自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜。它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能降低发生一些潜在错误的可能。LINQ包括ASP.NET MVC中的很多功能都是用Lambda实现的。我只能说自从用了Lambda,我腰也不酸了,腿也不疼了,手指也不抽筋了,就连写代码bug都少了。小伙伴们,你们今天用Lambda了么?但是你真的了解它么?今天我们就来好好的认识一下吧。
本文会介绍到一些Lambda的基础知识,然后会有一个小小的性能测试对比Lambda表达式和普通方法的性能,接着我们会通过IL来深入了解Lambda到底是什么,最后我们将用Lambda表达式来实现一些JavaScript里面比较常见的模式。
了解Lambda
在.NET 1.0的时候,大家都知道我们经常用到的是委托。有了委托呢,我们就可以像传递变量一样的传递方法。在一定程序上来讲,委托是一种强类型的托管的方法指针,曾经也一时被我们用的那叫一个广泛呀,但是总的来说委托使用起来还是有一些繁琐。来看看使用一个委托一共要以下几个步骤:
- 用delegate关键字创建一个委托,包括声明返回值和参数类型
- 使用的地方接收这个委托
- 创建这个委托的实例并指定一个返回值和参数类型匹配的方法传递过去
复杂吗?好吧,也许06年你说不复杂,但是现在,真的挺复杂的。
后来,幸运的是.NET 2.0为了们带来了泛型。于是我们有了泛型类,泛型方法,更重要的是泛型委托。最终 在.NET3.5的时候,我们Microsoft的兄弟们终于意识到其实我们只需要2个泛型委托(使用了重载)就可以覆盖99%的使用场景了。
- Action 没有输入参数和返回值的泛型委托
- Action<T1, …, T16> 可以接收1个到16个参数的无返回值泛型委托
- Func<T1, …, T16, Tout> 可以接收0到16个参数并且有返回值的泛型委托
这样我们就可以跳过上面的第一步了,不过第2步还是必须的,只是用Action或者Func替换了。别忘了在.NET2.0的时候我们还有匿名方法,虽然它没怎么流行起来,但是我们也给它 一个露脸的机会。
1
2
3
|
Func< double , double > delegate ( double x) return x } |
最后,终于轮到我们的Lambda优雅的登场了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// Action "Hello ); // Func< double , double > // Func< double , double , double > // Action< double , double > // Func< double [], double [], double > { var dim var sum for ( var i sum return sum; }; // Func< double , double , double >> { var sum /* return sum; }; |
从上面的代码中我们可以看出:
- 如果只有一个参数,不需要写()
- 如果只有一条执行语句,并且我们要返回它,就不需要{},并且不用写return
- Lambda可以异步执行,只要在前面加上async关键字即可
- Var关键字在大多数情况下都不能使用
当然,关于最后一条,以下这些情况下我们还是可以用var关键字的。原因很简单,我们告诉编译器,后面是个什么类型就可以了。
1
2
3
4
5
6
7
8
9
|
Func< double , double > double x) Func< string , int > string s) Action< decimal , string > decimal x, string s) { var sqz Console.WriteLine( "Information , }; |
现在,我们已经知道Lambda的一些基本用法了,如果仅仅就这些东西,那就不叫快乐的Lambda表达式了,让我们看看下面的代码。
1
2
3
4
5
|
var a Func< int , int > var result1 //50 a var result2 //100 |
是不是有一点感觉了?我们可以在Lambda表达式中用到外面的变量,没错,也就是传说中的闭包啦。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
void DoSomeStuff() { var coeff Func< int , int > Action { coeff }; var result1 ModifyStuff(modifier); var result2 } int DoMoreStuff(Func< int , int > { return computer(5); } void ModifyStuff(Action { modifier(); } |
在上面的代码中,DoSomeStuff方法里面的变量coeff实际是由外部方法ModifyStuff修改的,也就是说ModifyStuff这个方法拥有了访问DoSomeStuff里面一个局部变量的能力。它是如何做到的?我们马上会说的J。当然,这个变量作用域的问题也是在使用闭包时应该注意的地方,稍有不慎就有可能会引发你想不到的后果。看看下面这个你就知道了。
1
2
3
4
5
6
7
8
9
|
var buttons new Button[10]; for ( var i { var button new Button(); button.Text ". ; button.OnClick buttons[i] } |
猜猜你点击这些按钮的结果是什么?是”1, 2, 3…”。但是,其实真正的结果是全部都显示10。为什么?不明觉历了吧?那么如果避免这种情况呢?
1
2
3
4
5
|
var button new Button(); var index button.Text ". ; button.OnClick buttons[i] |
其实做法很简单,就是在for的循环里面把当前的i保存下来,那么每一个表达式里面存储的值就不一样了。
接下来,我们整点高级的货,和Lambda息息相关的表达式(Expression)。为什么说什么息息相关,因为我们可以用一个Expression将一个Lambda保存起来。并且允许我们在运行时去解释这个Lambda表达式。来看一下下面简单的代码:
1
2
3
|
Expression<Func<MyModel, int >> var member as MemberExpression; var propertyName |
这个的确是Expression最简单的用法之一,我们用expr存储了后面的表达式。编译器会为我们生成表达式树,在表达式树中包括了一个元数据像参数的类型,名称还有方法体等等。在LINQ TO SQL中就是通过这种方法将我们设置的条件通过where扩展方法传递给后面的LINQ Provider进行解释的,而LINQ Provider解释的过程实际上就是将表达式树转换成SQL语句的过程。
Lambda表达式的性能
关于Lambda性能的问题,我们首先可能会问它是比普通的方法快呢?还是慢呢?接下来我们就来一探究竟。首先我们通过一段代码来测试一下普通方法和Lambda表达 式之间的性能差异。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
class StandardBenchmark { const int LENGTH static double [] static double [] static void Init() { var r new Random(); A new double [LENGTH]; B new double [LENGTH]; for ( var i { A[i] B[i] } } static long LambdaBenchmark() { Func< double > { var sum for ( var i sum return sum; }; var iterations new double [100]; var timing new Stopwatch(); timing.Start(); for ( var j iterations[j] timing.Stop(); Console.WriteLine( "Time , return timing.ElapsedMilliseconds; } static long NormalBenchmark() { var iterations new double [100]; var timing new Stopwatch(); timing.Start(); for ( var j iterations[j] timing.Stop(); Console.WriteLine( "Time , return timing.ElapsedMilliseconds; } static double NormalPerform() { var sum for ( var i sum return sum; } } } |
代码很简单,我们通过执行同样的代码来比较,一个放在Lambda表达式里,一个放在普通的方法里面。通过4次测试得到如下结果:
Lambda Normal-Method
70ms 84ms
73ms 69ms
92ms 71ms
87ms 74ms
按理来说,Lambda应该是要比普通方法慢很小一点点的,但是不明白第一次的时候为什么Lambda会比普通方法还快一点。- -!不过通过这样的对比我想至少可以说明Lambda和普通方法之间的性能其实几乎是没有区别的。
那么Lambda在经过编译之后会变成什么样子呢?让LINQPad告诉你。
上图中的Lambda表达式是这样的:
1
2
3
4
|
Action< string > { Console.WriteLine(s); // }; |
对应的普通方法的写法是这样的:
1
2
3
4
|
void DoSomethingNormal( string s) { Console.WriteLine(s); } |
上面两段代码生成的IL代码呢?是这样地:
1
2
3
4
5
6
7
8
9
10
11
12
|
DoSomethingNormal: IL_0000: IL_0001: IL_0002: IL_0007: IL_0008: <Main>b__0: IL_0000: IL_0001: IL_0002: IL_0007: IL_0008: |
最大的不同就是方法的名称以及方法的使用而不是声明,声明实际上是一样的。通过上面的IL代码我们可以看出,这个表达式实际被编译器取了一个名称,同样被放在了当前的类里面。所以实际上,和我们调类里面的方法没有什么两样。下面这张图说明了这个编译的过程:
上面的代码中没有用到外部变量,接下来我们来看另外一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void Main() { int local Action< string > Console.WriteLine(s }; global DoSomethingLambda( "Test ); DoSomethingNormal( "Test ); } int global; void DoSomethingNormal( string s) { Console.WriteLine(s } |
这次的IL代码会有什么不同么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
IL_0000: IL_0005: IL_0006: IL_0007: IL_0008: IL_0009: IL_000E: IL_000F: IL_0015: IL_001A: IL_001B: IL_001C: IL_001D: IL_0022: IL_0027: IL_0028: "Test IL_002D: IL_0032: IL_0033: IL_0034: "Test IL_0039: IL_003E: DoSomethingNormal: IL_0000: IL_0001: IL_0002: IL_0003: IL_0008: IL_000D: IL_0012: IL_0017: IL_0018: <>c__DisplayClass1.<Main>b__0: IL_0000: IL_0001: IL_0002: IL_0003: IL_0008: IL_000D: IL_0012: IL_0017: IL_0018: <>c__DisplayClass1..ctor: IL_0000: IL_0001: IL_0006: |
你发现了吗?两个方法所编译出来的内容是一样的, DoSomtingNormal和<>c__DisplayClass1.<Main>b__0,它们里面的内容是一样的。但是最大的不一样,请注意了。当我们的Lambda表达式里面用到了外部变量的时候,编译器会为这个Lambda生成一个类,在这个类中包含了我们表达式方法。在使用这个Lambda表达式的地方呢,实际上是new了这个类的一个实例进行调用。这样的话,我们表达式里面的外部变量,也就是上面代码中用到的local实际上是以一个全局变量的身份存在于这个实例中的。
用Lambda表达式实现一些在JavaScript中流行的模式
说到JavaScript,最近几年真是风声水起。不光可以应用所有我们软件工程现存的一些设计模式,并且由于它的灵活性,还有一些由于JavaScript特性而产生的模式。比如说模块化,立即执行方法体等。.NET由于是强类型编译型的语言,灵活性自然不如JavaScript,但是这并不意味着JavaScript能做的事情.NET就不能做,下面我们就来实现一些JavaScript中好玩的写法。
回调模式
回调模式也并非JavaScript特有,其实在.NET1.0的时候,我们就可以用委托来实现回调了。但是今天我们要实现的回调可就不一样了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
void CreateTextBox() { var tb new TextBox(); tb.IsReadOnly true ; tb.Text "Please ; DoSomeStuff(() tb.Text string .Empty; tb.IsReadOnly false ; }); } void DoSomeStuff(Action { // callback(); } |
上面的代码中,我们在DoSomeStuff完成之后,再做一些事情。这种写法在JavaScript中是很常见的,jQuery中的Ajax的oncompleted, onsuccess不就是这样实现的么?又或者LINQ扩展方法中的foreach不也是这样的么?
返回方法
我们在JavaScript中可以直接return一个方法,在.net中虽然不能直接返回方法,但是我们可以返回一个表达式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
Func< string , string > string language) { switch (language.ToLower()) { case "fr" : return name return "Je + "." ; }; case "de" : return name return "Mein + "." ; }; default : return name return "My + "." ; }; } } void Main() { var lang "de" ; //Get var smn var name var sentence Console.WriteLine(sentence); } |
是不是有一种策略模式的感觉?这还不够完美,这一堆的switch case看着就心烦,让我们用Dictionary<TKey,TValue>来简化它。来看看来面这货:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static class Translations { static readonly Dictionary< string , string , string >> new Dictionary< string , string , string >>(); static Translations() { smnFunctions.Add( "fr" , "Je + "." ); smnFunctions.Add( "de" , "Mein + "." ); smnFunctions.Add( "en" , "My + "." ); } public static Func< string , string > string language) { //Check return smnFunctions[language]; } } |
自定义型方法
自定义型方法在JavaScript中比较常见,主要实现思路是这个方法被设置成一个属性。在给这个属性附值,甚至执行过程中我们可以随时更改这个属性的指向,从而达到改变这个方法的目地。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class SomeClass { public Func< int > { get ; private set ; } int prime; public SomeClass { NextPrime prime NextPrime // return prime; }; return prime; } } } |
上面的代码中当NextPrime第一次被调用的时候是2,与此同时,我们更改了NextPrime,我们可以把它指向另外的方法,和JavaScrtip的灵活性比起来也不差吧?如果你还不满意 ,那下面的代码应该能满足你。
1
2
3
4
5
6
7
8
9
|
Action< int > if (i loopBody //把loopBody指向别的方法 /* }; for ( int j loopBody(j); |
在调用的地方我们不用考虑太多,然后这个方法本身就具有调优性了。我们原来的做法可能是在判断i==1000之后直接写上相应的代码,那么和现在的把该方法指向另外一个方法有什么区别呢?
自执行方法
JavaScript 中的自执行方法有以下几个优势:
- 不会污染全局环境
- 保证自执行里面的方法只会被执行一次
- 解释完立即执行
在C#中我们也可以有自执行的方法:
1
2
3
|
(() // })(); |
上面的是没有参数的,如果你想要加入参数,也非常的简单:
1
2
3
|
(( string s, int no) // })( "Example" , |
.NET4.5最闪的新功能是什么?async?这里也可以
1
2
3
4
5
|
await string s, int no) // })( "Example" , // |
对象即时初始化
大家知道.NET为我们提供了匿名对象,这使用我们可以像在JavaScript里面一样随意的创建我们想要对象。但是别忘了,JavaScript里面可以不仅可以放入数据,还可以放入方法,.NET可以么?要相信,Microsoft不会让我们失望的。
1
2
3
4
5
6
7
8
9
10
11
|
//Create var person new { Name "Jesse" , Age Ask string question) Console.WriteLine( "The + "` ); } }; //Execute person.Ask( "Why ); |
但是如果你真的是运行这段代码,是会抛出异常的。问题就在这里,Lambda表达式是不允许赋值给匿名对象的。但是委托可以,所以在这里我们只需要告诉编译器,我是一个什么类型的委托即可。
1
2
3
4
5
6
7
|
var person new { Name "Florian" , Age Ask string >)(( string question) Console.WriteLine( "The + "` ); }) }; |
但是这里还有一个问题,如果我想在Ask方法里面去访问person的某一个属性,可以么?
1
2
3
4
5
6
7
8
|
var person new { Name "Jesse" , Age Ask string >)(( string question) Console.WriteLine( "The + "' + })) }; |
结果是连编译都通不过,因为person在我们的Lambda表达式这里还是没有定义的,当然不允许使用了,但是在JavaScript里面是没有问题的,怎么办呢?.NET能行么?当然行,既然它要提前定义,我们就提前定义好了。
1
2
3
4
5
6
7
8
9
10
11
|
dynamic null ; person new { Name "Jesse" , Age Ask string >)(( string question) Console.WriteLine( "The + "` + "." ); }) }; //Execute person.Ask( "Why ); |
运行时分支
这个模式和自定义型方法有点类似,唯一的不同是它不是在定义自己,而是在定义别的方法。当然,只有当这个方法基于属性定义的时候才有这种实现的可能。
1
2
3
4
5
6
7
8
9
10
11
|
public Action get ; private set ; public void ReadSettings(Settings { /* if (settings.EnableAutoSave) AutoSave /* }; else AutoSave //Just } |
可能有人会觉得这个没什么,但是仔细想想,你在外面只需要调用AutoSave就可以了,其它的都不用管。而这个AutoSave,也不用每次执行的时候都需要去检查配置文件了。
总结
Lambda表达式在最后编译之后实质是一个方法,而我们声明Lambda表达式呢实质上是以委托的形式传递的。当然我们还可以通过泛型表达式Expression来传递。通过Lambda表达式形成闭包,可以做很多事情,但是有一些用法现在还存在争议,本文只是做一个概述 :),如果有不妥,还请拍砖。谢谢支持 :)
还有更多Lambda表达式的新鲜玩法,请移步: 背后的故事之 - 快乐的Lambda表达式(二)
原文链接: http://www.codeproject.com/Articles/507985/Way-to-Lambda
快乐的Lambda表达式(一)的更多相关文章
- 背后的故事之 - 快乐的Lambda表达式(一)
快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...
- 背后的故事之 - 快乐的Lambda表达式(二)
快乐的Lambda表达式 上一篇 背后的故事之 - 快乐的Lambda表达式(一)我们由浅入深的分析了一下Lambda表达式.知道了它和委托以及普通方法的区别,并且通过测试对比他们之间的性能,然后我们 ...
- 【转】背后的故事之 - 快乐的Lambda表达式(二)
快乐的Lambda表达式 上一篇 背后的故事之 - 快乐的Lambda表达式(一)我们由浅入深的分析了一下Lambda表达式.知道了它和委托以及普通方法的区别,并且通过测试对比他们之间的性能,然后我们 ...
- 【转】背后的故事之 - 快乐的Lambda表达式(一)
快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...
- 快乐的Lambda表达式(二)
转载:http://www.cnblogs.com/jesse2013/p/happylambda-part2.html 快乐的Lambda表达式 上一篇 背后的故事之 - 快乐的Lambda表达式( ...
- C#中的Lambda表达式和表达式树
在C# 2.0中,通过方法组转换和匿名方法,使委托的实现得到了极大的简化.但是,匿名方法仍然有些臃肿,而且当代码中充满了匿名方法的时候,可读性可能就会受到影响.C# 3.0中出现的Lambda表达式在 ...
- python内置函数,lambda表达式,文件读写
Lambda表达式: lambda是个匿名函数,自动加return返回 a={ 6:2,8:0, 1:4,-5:6,99:11,4:22} print(sorted(a.items()))#按key排 ...
- 你知道C#中的Lambda表达式的演化过程吗?
那得从很久很久以前说起了,记得那个时候... 懵懂的记得从前有个叫委托的东西是那么的高深难懂. 委托的使用 例一: 什么是委托? 个人理解:用来传递方法的类型.(用来传递数字的类型有int.float ...
- Linq表达式、Lambda表达式你更喜欢哪个?
什么是Linq表达式?什么是Lambda表达式? 如图: 由此可见Linq表达式和Lambda表达式并没有什么可比性. 那与Lambda表达式相关的整条语句称作什么呢?在微软并没有给出官方的命名,在& ...
随机推荐
- Android之TCP服务器编程
推荐一个学java或C++的网站http://www.weixueyuan.net/,本来想自己学了总结出来再写博客,现在没时间,打字太慢!!!!,又想让这好东西让许多人知道. 关于网络通信:每一台电 ...
- linux下比较两个文件:diff、 vimdiff
diff更加具体的命令,比如file1, file2 > diff -u file1 file2 > vimdiff file1 file2 vimdiff 有点类似于 vim - ...
- CF809E Surprise me! 莫比乌斯反演、虚树
传送门 简化题意:给出一棵\(n\)个点的树,编号为\(1\)到\(n\),第\(i\)个点的点权为\(a_i\),保证序列\(a_i\)是一个\(1\)到\(n\)的排列,求 \[ \frac{1} ...
- Ionic 中badge的应用
app中如果有服务端推送过来的消息,用户没有查看的话,出现一个数字提醒,类似微信的那种效果. 在Ionic中的实现过程还是很简单的: <ion-tab title="首页" ...
- JS 去除重复元素的方法
Array.prototype.del = function () { var a = {}, c = [], l = this.length; ; i < l; i++) { var b = ...
- spring boot 在不同环境下读取不同配置文件的一种方式
在工程中,通常有根据不同的环境读取不同配置文件的需求,对于spring boot 来说,默认读取的是application.yml 或者 application.properties.为了区分不同的环 ...
- layui表格和弹出框的简单示例
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...
- 暴雪《争霸艾泽拉斯》*采用自适应 SSAO
在实时渲染过程中,屏幕空间环境光遮蔽 (SSAO) 常用于打造小范围环境光效果和接触阴影效果.它用于许多现代游戏,通常占用 5% 到 10% 的帧时间.在<争霸艾泽拉斯>* 游戏开发过程中 ...
- C. Party Lemonade
链接 [http://codeforces.com/group/1EzrFFyOc0/contest/913/problem/C] 分析 看代码,巧妙的贪心 代码 #include<bits/s ...
- 《Linux内核设计与分析》第六周读书笔记——第三章
<Linux内核设计与实现>第六周读书笔记——第三章 20135301张忻估算学习时间:共2.5小时读书:2.0代码:0作业:0博客:0.5实际学习时间:共3.0小时读书:2.0代码:0作 ...