在上一篇文章中我们简单探讨了.NET 1.x和.NET 2.0中委托表现形式的变化,以及.NET 2.0中匿名方法的优势、目的及注意事项。那么现在我们来谈一下.NET 3.5(C# 3.0)中,委托的表现形式又演变成了什么样子,还有什么特点和作用。

.NET 3.5中委托的写法(Lambda表达式)

Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。例如,以下便是一个使用Lambda表达式定义了委托的示例1

Func<int, int, int> max = (int a, int b) =>
{if (a > b){return a;}else{return b;}
};

与上文使用delegate定义匿名方法的作用相同,Lambda表达式的作用也是为了定义一个匿名方法。因此,下面使用delegate的代码和上面是等价的:

Func<int, int, int> max = delegate(int a, int b)
{if (a > b){return a;}else{return b;}
};

那么您可能就会问,这样看来Lambda表达式又有什么意义呢?Lambda表达式的意义便是它可以写的非常简单,例如之前的Lambda表达式可以简写成这样:

Func<int, int, int> max =  =>
{if (a > b){return a;}else{return b;}
};

由于我们已经注明max的类型是Func<int, int, int>,因此C#编译器可以明确地知道a和b都是int类型,于是我们就可以省下参数之前的类型信息。这个特性叫做“类型推演”,也就是指编译器可以自动知道某些成员的类型2。请不要轻易认为这个小小的改进意义不大,事实上,您会发现Lambda表达式的优势都是由这一点一滴的细节构成的。那么我们再来一次改变:

Func<int, int, int> max = (a, b) => a > b ? a : b;

如果Lambda表达式的body是一个表达式(expression),而不是语句(statement)的话,那么它的body就可以省略大括号和return关键字。此外,如果Lambda表达式只包含一个参数的话,则参数列表的括号也可以省略,如下:

Func<int, bool> positive = a => a > 0;

如今的写法是不是非常简单?那么我们来看看,如果是使用delegate关键字来创建的话会成为什么样子:

Func<int, bool> positive = delegate(int a)
{return a > 0;
};

您马上就可以意识到,这一行和多行的区别,这几个关键字和括号的省略,会使得编程世界一下子变得大为不同。

当然,Lambda表达式也并不是可以完全替代delegate写法,例如带ref和out关键字的匿名方法,就必须使用.NET 2.0中的delegate才能构造出来了。

使用示例一

Lambda表达式的增强在于“语义”二字。“语义”是指代码所表现出来的含义,说的更通俗一些,便是指一段代码给阅读者的“感觉”如何。为了说明这个例子,我们还是使用示例来说明问题。

第一个例子是这样的:“请写一个方法,输入一个表示整型的字符串列表,并返回一个列表,包含其中偶数的平方,并且需要按照平方后的结果排序”。很简单,不是吗?相信您一定可以一蹴而就:

static List<int> GetSquaresOfPositive(List<string> strList)
{List<int> intList = new List<int>();foreach (var s in strList) intList.Add(Int32.Parse(s));List<int> evenList = new List<int>();foreach (int i in intList){if (i % 2 == 0) evenList.Add(i);}List<int> squareList = new List<int>();foreach (int i in evenList) squareList.Add(i * i);squareList.Sort();return squareList;
}

我想问一下,这段代码给您的感觉是什么?它给我的感觉是:做了很多事情。有哪些呢?

  1. 新建一个整数列表intList,把参数strList中所有元素转化为整型保存起来。
  2. 新建一个整数列表evenList,把intList中的偶数保存起来。
  3. 新建一个整数列表squareList,把evenList中所有数字的平方保存起来。
  4. 将squareList排序。
  5. 返回squareList。

您可能会问:“当然如此,还能怎么样?”。事实上,如果使用了Lambda表达式,代码就简单多了:

static List<int> GetSquaresOfPositiveByLambda(List<string> strList)
{return strList.Select(s => Int32.Parse(s)) // 转成整数.Where(i => i % 2 == 0) // 找出所有偶数.Select(i => i * i) // 算出每个数的平方.OrderBy(i => i) // 按照元素自身排序.ToList(); // 构造一个List
}

配合.NET 3.5中定义的扩展方法,这段代码可谓“一气呵成”(在实际编码过程中,老赵更倾向于把这种简短的“递进式”代码写作一行)。那么这行代码的“语义”又有什么变化呢?在这里,“语义”的变化在于代码的关注点从“怎么做”变成了“做什么”。这就是Lambda表达式的优势。

在第一个方法中,我们构造了多个容器,然后做一些转化,过滤,并且向容器填充内容。其实这些都是“怎么做”,也就是所谓的“how (to do)”。但是这些代码并不能直接表示我们想要做的事情,我们想要做的事情其实是“得到XXX”,“筛选出YYY”,而不是“创建容器”,“添加元素”等操作。

在使用Lambda表达式的实现中,代码变得“声明式(declarative)”了许多。所谓“声明式”,便是“声称代码在做什么”,而不像“命令式(imperative)”的代码在“操作代码怎么做”。换句话说,“声明式”关注的是“做什么”,是指“what (to do)”。上面这段声明式的代码,其语义则变成了:

  1. 把字符串转化为整数
  2. 筛选出所有偶数
  3. 把每个偶数平方一下
  4. 按照平方结果自身排序
  5. 生成一个列表

至于其中具体是怎么实现的,有没有构造新的容器,又是怎么向容器里添加元素的……这些细节,使用Lambda表达式的代码一概不会关心——这又不是我们想要做的事情,为什么要关心它呢?

虽然扩展方法功不可没,但我认为,Lambda表达式在这里的重要程度尤胜前者,因为它负责了最关键的“语义”。试想,“i => i * i”给您的感觉是什么呢?是构造了一个委托吗(当然,您一定知道在这里其实构造了一个匿名方法)?至少对我来说,它的含义是“把i变成i * i”;同样,“i => i % 2 == 0”给我的感觉是“(筛选标准为)i模2等于零”,而不是“构造一个委托,XXX时返回true,否则返回false”;更有趣的是,OrderBy(i => i)给我的感觉是“把i按照i自身排序”,而不是“一个返回i自身的委托”。这一切,都是在“声明”这段代码在“做什么”,而不是“怎么做”。

没错,“类型推演”,“省略括号”和“省略return关键字”可能的确都是些“细小”的功能,但也正是这些细微之处带来了编码方式上的关键性改变。

使用示例二

使用Lambda表达式还可以节省许多代码(相信您从第一个示例中也可以看出来了)。不过我认为,最省代码的部分更应该可能是其“分组”和“字典转化”等功能。因此,我们来看第二个示例。

这个示例可能更加贴近现实。不知您是否关注过某些书籍后面的“索引”,它其实就是“列出所有的关键字,根据其首字母进行分组,并且要求对每组内部的关键字进行排序”。简单说来,我们需要的其实是这么一个方法:

static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords) { ... }

想想看,您会怎么做?其实不难(作为示例,我们这里只关注小写英文,也不关心重复关键字这种特殊情况):

static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords)
{// 定义字典var result = new Dictionary<char, List<string>>();// 填充字典foreach (var kw in keywords){var firstChar = kw[0];List<string> groupKeywords;if (!result.TryGetValue(firstChar, out groupKeywords)){groupKeywords = new List<string>();result.Add(firstChar, groupKeywords);}groupKeywords.Add(kw);}// 为每个分组排序foreach (var groupKeywords in result.Values){groupKeywords.Sort();}return result;
}

那么如果利用Lambda表达式及.NET框架中定义的扩展方法,代码又会变成什么样呢?请看:

static Dictionary<char, List<string>> GetIndexByLambda(IEnumerable<string> keywords)
{return keywords.GroupBy(k => k[0]) // 按照首字母分组.ToDictionary( // 构造字典g => g.Key, // 以每组的Key作为键g => g.OrderBy(k => k).ToList()); // 对每组排序并生成列表
}

光从代码数量上来看,前者便是后者的好几倍。而有关“声明式”,“what”等可读性方面的优势就不再重复了,个人认为它比上一个例子给人的“震撼”有过之而无不及。

试想,如果我们把GetIndexByLambda方法中的Lambda表达式改成.NET 2.0中delegate形式的写法:

static Dictionary<char, List<string>> GetIndexByDelegate(IEnumerable<string> keywords)
{return keywords.GroupBy(delegate(string k) { return k[0]; }).ToDictionary(delegate(IGrouping<char, string> g) { return g.Key; },delegate(IGrouping<char, string> g){return g.OrderBy(delegate(string s) { return s; }).ToList();});
}

您愿意编写这样的代码吗?

因此,Lambda表达式在这里还是起着决定性的作用。事实上正是因为有了Lambda表达式,.NET中的一些函数式编程特性才被真正推广开来。“语言特性”决定“编程方式”的确非常有道理。这一点上Java是一个很好的反例:从理论上说,Java也有“内联”的写法,但是C#的使用快感在Java那边还只能是个梦。试想GetIndexByLambda在Java中会是什么情况3

public Dictionary<Char, List<String>> GetIndexInJava(Enumerable<String> keywords)
{return keywords.GroupBy(new Func<String, Char> {public Char execute(String s) { return s.charAt(0); }}).ToDictionary(new Func<Grouping<Char, String>, Char> {public Char execute(IGrouping<Char, String> g) { return g.getKey(); }},new Func<Grouping<Char, String>, List<string>> {public List<String> execute(IGrouping<Char, String> g){return g.OrderBy(new Func<String, String> {public String execute(String s) { return s; }}).ToList();}});
}

一股语法噪音的气息扑面而来,让人无法抵挡。由于Java中的匿名类型语法(即上面这种内联写法)连类型信息(new Func<String, Char>{ ... }这样的代码)都无法省去,因此给人非常繁琐的感觉。面对这样的代码,您可能会有和我一样的想法:“还不如最普通的写法啊”。没错,这种函数式编程的风格,由于缺乏语言特性支持,实在不适合在Java语言中使用。事实上,这种内联写法很早就出现了(至少在02、03年我还在使用Java的时候就已经有了),但是那么多年下来一点改进都没有。而Lambda表达式出现之后,社区中立即跟进了大量项目,如Moq,Fluent NHibernate等等,充分运用了C# 3.0的这一新特性。难道这还不够说明问题吗?

对了,再次推荐一下Scala语言,它的代码可以写的和C#一样漂亮。我不是Java平台的粉丝,更是Java语言的忠实反对者,但是我对Java平台上的Scala语言和开源项目都抱有强烈的好感。

既然谈到了函数式编程,那么就顺便再多说几句。其实这两个例子都有浓厚的函数式编程影子在里面,例如,对于函数试编程来说,Where常被叫做filter,Select常被叫做map。而.NET 3.5中定义的另一些方法在函数式编程里都有体现(如Aggregate相当于fold)。如果您对这方面感兴趣,可以关注Matthew Poswysocki提出的Functional C#类库。

总结

既可以提高可读性,又能够减少代码数量,我实在找不出任何理由拒绝Lambda表达式。

哦,对了,您可能会提到“性能”,这的确也是一个重要的方面,不过关于这个话题我们下次再谈。受篇幅限制,原本计划的“上”“下”两篇这次又不得不拆开了。至于其他的内容,也等讨论完性能问题之后再说吧。

当然,世界上没有东西是完美的,如果您觉得Lambda表达式在某些时候会给您带来“危害”,那么也不妨使用delegate代替Lambda表达式。例如,为了代码清晰,在某些时候还是显式地指明参数类型比较好。不过对我而言,在任何情况下我都会使用Lambda表达式——最多使用“(int a, string b) =>”的形式咯,我想总比“delegate(int a, string b)”要统一、省事一些吧。

相关文章

  • 从.NET中委托写法的演变谈开去(上):委托与匿名方法
  • 从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势
  • 从.NET中委托写法的演变谈开去(下):性能相关

注1:严格说来,这里的body是一个“语句(statement)”,而不是“表达式(expression)”。因为一个委托其实是一个方法,因此使用Lambda来表示一个委托,其中必然要包含“语句”。不过在目前的C#中,Lambda表达式还有一个作用是构造一颗“表达式树”,而目前的C#编译器只能构造“表达式树”而不是“语句树”。

注2:事实上,在.NET 2.0使用delegate关键字定义匿名方法时已经可以有些许“类型推演”的意味了——虽然还是必须写明参数的类型,但是我们已经可以省略委托的类型了,不是吗?

注3:除非我们补充Func、Enumerable,Dictionary,Grouping等类型及API,否则这段代码在Java中是无法编译通过的。事实上,这段Java代码是我在记事本中写出来的。不过这个形式完全正确。

转载于:https://www.cnblogs.com/JeffreyZhao/archive/2009/08/07/from-delegate-to-others-2.html

从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势相关推荐

  1. 一起谈.NET技术,从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势...

    在上一篇文章中我们简单探讨了.NET 1.x和.NET 2.0中委托表现形式的变化,以及.NET 2.0中匿名方法的优势.目的及注意事项.那么现在我们来谈一下.NET 3.5(C# 3.0)中,委托的 ...

  2. .NET中委托写法的演变(上):委托与匿名方法

    可能有一件事情需要说在前面,那就是:委托本身其实从来没有改变过,改变的一直都是委托的"写法".因此更确切地说,改变的只是"编译器".而本文所有内容都用C#来实现 ...

  3. C# 匿名委托、匿名方法、匿名对象、Lambda表达式

    C# 匿名委托.匿名方法.匿名对象.Lambda表达式 原文:C# 匿名委托.匿名方法.匿名对象.Lambda表达式 一.匿名类型 可通过使用 new 运算符和对象初始值创建匿名类型. 示例: var ...

  4. CAAI演讲实录丨李德毅院士:交互认知——从图灵测试的漏洞谈开去

    8月26日至27日,在中国科学技术协会.中国科学院的指导下,由中国人工智能学会发起主办.中科院自动化研究所与CSDN共同承办的2016中国人工智能大会(CCAI 2016)在北京辽宁大厦盛大召开,这也 ...

  5. 【CCAI 2016】李德毅院士:交互认知——从图灵测试的漏洞谈开去

    8月26日至27日,在中国科学技术协会.中国科学院的指导下,由中国人工智能学会发起主办.中科院自动化研究所与CSDN共同承办的2016中国人工智能大会(CCAI 2016)在北京辽宁大厦盛大召开,这也 ...

  6. c++ 调用system 不显示黑框_Java回调的四种写法:反射+直接调用+接口调用+Lambda表达式...

    疫情期间"闭关修炼",吃透这本Java核心知识,跳槽面试不心慌 2020"闭关"跳槽季,啃透分布式三大技术:限流.缓存.通讯 2020春招必备:MySQL(20 ...

  7. 中俄国际社区大佬共话Java,苦Lambda表达式久矣?

    众所周知,Java作为一门非常成熟的语言,国内拥趸者众多,但随着后浪们的崛起,如今的Java在国际上是否还占据主流地位,Java开发的Spring框架和Spring Boot系统是否还是流行技术?现在 ...

  8. writedouble_从MySQL中的double write问题说开去

    有句话说得好,世上只有两种工具,一种是被人骂的,另一种是没人用的.被骂得越多,侧面反映出关注度越高,使用率越高,越用越成熟,这一点上, MySQL就是一个很不错的例子.而MySQL可支持的存储引擎很多 ...

  9. html中加一个空行,浅谈HTML代码中的空格和空行

    HTML 代码中的所有连续的空格或空行(换行)都会被显示为一个空格. 例子1:(文本内容中的连续空格) 代码 XML/HTML Code复制内容到剪贴板 这段文本中,输入连续的空格           ...

最新文章

  1. 经典分类:线性判别分析模型!
  2. css美化单选款、复选框
  3. linux网口驱动实现(待续)
  4. java内存模型(JMM)和happens-before
  5. 从技术分工的角度来看996.ICU
  6. svd降维 python案例_SVD(奇异值分解)Python实现
  7. android输入自动补全,Android用户输入自动提示控件AutoCompleteTextView使用方法
  8. Like Sunday, Like Rain - JavaScript运算符优先级
  9. 万年历插件软件测试,一个计算万年历的简单程序
  10. 2月26日 光流,跟踪算法的尝试
  11. 红旗6.0 sp2 永中office2009不能启动的原因
  12. c语言字段宽度,2.6.3 控制输出的字段宽度
  13. 如何将已有图片做成透明水印_如何给图片制作透明水印
  14. Implement AWS SQS and Lambda to decouple process flow
  15. 一文告诉你IT行业什么方向赚钱,需求多
  16. 单片机延时程序分析(汇编详细版)
  17. 笔记本开不了机怎么办?
  18. Python软件编程等级考试三级——20220618
  19. 马小看前端( 在开发中遇到问题如何解决 )
  20. 案例研究:什么是自动驾驶?

热门文章

  1. 用好这个数据分析工具,BI平台建设少费一半力
  2. 帆软报表登录背后得逻辑
  3. 拦截器CacheInterceptor
  4. 计算机中加密文件找不见,文件夹保护3000找不到文件夹加密记录怎么办?
  5. mysql中国菜刀连接_中国菜刀使用方法以及小技巧
  6. java数学系统总结与展望_总结与展望
  7. python如何在没有环境的电脑上执行_没安装python的电脑可以运行python写的程序吗 python语言编写的程...
  8. 嵌入不同源的页面_嵌入式技术课程教与学(教学大纲和试卷)
  9. 计算机算法设计与分析 大学生电影节观影问题
  10. pytorch学习笔记(三十九):Fine-Tuning