C#函数式程序设计之函数、委托和Lambda表达式

C#函数式程序设计之函数、委托和Lambda表达式

 

相信很多人都听说过函数式编程,提到函数式程序设计,脑海里涌现出来更多的是Lisp、Haskell等语言,而C#,似乎我们并不把它当成函数式语言,其实,函数式程序设计并不是只针对某个特定的程序设计语言,而C#,也正一步步使用函数式丰富自己的语言结构,帮助人们更好的实现期望的结果。

函数式程序设计

函数式程序设计把重点放在函数的应用上,函数式程序设计人员以函数为基本模块来建立新函数,这并不是说没有其他语言的成分,而是说函数是程序体系创建的主要构造。

引用透明(Referential transparency)是函数式程序设计领域中的一个重要思想。一个引用透明的函数的返回值只取决于传递给它的参数的值。这正好与指令程序设计的基本思想相反。在指令程序设计中,程序的状态通常会影响函数的返回值。引用透明的函数的数学意义仅存在于函数式程序设计中,这样的函数称为纯函数,没有副作用。

函数式程序设计属于一种定向思维。如果我们愿意按某种方式去思考,则它可以给我们提供有趣的解决方案或者至少思考的源头,它们都与当前程序设计的许多实际问题有关。

C#无法做到像Lisp、Haskell或同属于.NET平台的F#那样很容易实现函数式程序设计,这点我们必须承认,但从各方面来讲,用C#实现函数式程序设计确实是有意义的。

C#函数式程序设计基础之函数与方法

由于C#的函数只能出现在类中,因此它们通常被称为方法。方法可以接受若干个参数,并且可以有一个返回值。

与许多面向对象语言一样,C#类中的方法可以是实例方法,也可以是类方法。而在纯函数式程序设计中,没有类,也没有类的实例——当然,有很多方法保存数据,但通常不是用类来保存数据,它们总是在许多方面表现出不同。

在面向对象环境中,所有其他元素只能出现在类和对象的内部(对象是类实例的另一个说法);而在函数式程序设计中,所有其他元素都出现在函数内部。有些数据保存在函数的局部变量中,就像C#那样定义在方法内部的变量,但这并不是保存数据最理想的方法。

F#把类级别的成员当成全局成员,同时由于得到特殊语法的支持,程序员不需要考虑实际发生的“转换”过程,遗憾的是,在C#中无法实现这一点,但是解决方法是一样的。

为了调用全局级的函数(或者任何其他作用域的函数),必须在类内创建类级别的成员。这些成员要用static关键字。由于它们都封装在类中,因此类中的成员有不同的可见度。大多数函数式设计环境都有不同的封装级别——如模块级或命名空间级——因此除了C#中一些比较复杂的语法外,实际上两者没有多大的区别。

有些函数式语言使用顶级函数或者允许导入模块或命名空间,这样就不需要函数调用的修饰符:

1
DoSomething "string paramers"

在C#中,这样的调用总是需要一个修饰符,即类名,除非这个函数出现在同一个类的内部:

1
SomeClass.DoSomething("string paramers");

C#函数式程序设计基础之重用函数

在计算机程序设计中,重用是一个非常重要的综合问题。函数并不是可重用性的唯一方法,特别在面向对象程序设计中,很快出现了其他方法。作为C#的一个内置功能,它只支持函数的重载作为函数级模块化的直接办法,C#4.0支持命名参数和可选参数,因此重载函数的解析过程变得相当复杂,特别当它与其他相关方法(如在方法调用时进行泛型类型推断)一起使用时。

下面举一个重载方法的简单例子:

 

在这个例子中,我们很清楚地看出为什么重载与重用有关:它允许程序员创建与原函数类似的新函数,同时尽可能利用原函数已有的功能。

C#函数式程序设计基础之匿名函数与Lambda表达式

并非所有的函数都重要到需要一个名称,一般而言,这些函数并不是类级别的函数,它们没有名称,这些函数的引用地址保存在变量中,因此只要有这些函数的引用地址就可以调用它们。

从技术上讲,匿名函数肯定要受到某些限制。很遗憾的是,其中之一就是它们不可以是泛型,它们也不可以用来实现迭代器。除此之外,匿名函数几乎可以包括所有做任何“正常”方法可以做的事情。

以上是C#2.0的代码,可以看出,关键字delegate委托代替了方法名。参数列表和方法体还是与前面一样。这个匿名方法也可以改写成如下形式,这里用了C#3.0的Lambda表达式语法:

这段代码较短,因为少了delegate关键字,方法体已经写成一行格式。Lambda表达式中的主体=>运算符右侧的部分。可以采取若干方法进一步简化代码。首先, 可以省略参数类型,因为编译器可以根据委托类型的声明语句推断出参数的类型:

其次,由于函数除了返回一个值外不执行任何操作,因此可以把函数体转换为表达式体,并且可以利用隐式返回:

表达式体很有用。因为有了它,在函数式程序中本来需要用函数实现的某个操作现在可以简化为一个表达式。与函数一样,表达式体也要接受参数并返回一个值。表达式体不可以包含任何与返回值求值无关的代码(即只要有一个返回值就行,遗憾的是,经常在表达式体中使用没有返回值的表达式)。

前面的例子如果使用其中一个泛型委托类型,就可以变成如下的形式:

这个委托需要接受两个object类型的参数,返回一个bool值。使用泛型委托类型的另一个好处是,它们的参数类型更容易看明白,因为它们在委托类型中采用显式声明,而且编译器可以为Lambda表达式推断出它们的类型。

使用Lambda表达式时,有一个细节需要牢记:只有当所有类型都确定后,编译器才会根据几个比较复杂的准则进行类型推断。编译器并不是总能正确地推断出类型,因此,如果所有的类型都确定了,编译器的要求就满足了:

在这个Lambda表达式中不可以使用var关键字,C#中,编译器必须能够在声明的位置推断出参数的类型,对于下面的语句则无法推断出参数的类型:

函数式程序设计语言要求,在所有与类型推断有关的情形中都需要像这样的显式说明。这在某些C#程序员看来是遗憾的。

C#函数式程序设计基础之扩展方法

扩展方法是静态类中用特殊方法表示的静态方法:

表示Concat是一个扩展方法的标志是在该方法的参数列表中使用this关键字。这个关键字是C#专有的,用于命令编译器给这个方法中增加ExtensionMethodAttribute属性。可以像调用静态方法那样调用扩展方法:

然而,由于它是扩展方法,因此也可以像下面这样调用:

当我们需要充分利用扩展方法的优点时,这种调用方法比较简单。

每个扩展方法都有一个可扩展的特定类型:第一个参数的类型,即用this标志的那个参数。这个标志只可以用于第一个参数,不可以用于其他参数。扩展方法的第一个参数可以是一个基类类型或者一个接口,甚至可以是System.Object中的对象。扩展方法也可以是泛型的,他们可以扩展泛型类型。

C#函数式程序设计基础之引用透明

在指令式程序设计中,这些模块的基本作用是防止代码重复,把代码分解成更容易管理的函数级模块。指令式程序设计的最大问题之一是随着时间的推移,模块会变得越来越大。由于指令式程序设计把重点放在执行序列上,因此函数和方法的引用总是不透明的。

引用透明:表达式可以用表达式的值取代而不会影响程序,也就是不会影响使用此替换操作的算法的最终结果。

 
 
分类: C#专题
 
 
 

C#函数式程序设计之函数、委托和Lambda表达式的更多相关文章

  1. 转载 C#匿名函数 委托和Lambda表达式

    转载原出处: http://blog.csdn.net/honantic/article/details/46331875 匿名函数 匿名函数(Anonymous Function)是表示“内联”方法 ...

  2. 深入学习C#匿名函数、委托、Lambda表达式、表达式树类型——Expression tree types

    匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...

  3. 匿名函数、委托和Lambda表达式

    匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...

  4. [深入学习C#] 匿名函数、委托和Lambda表达式

    匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...

  5. 系统预定义委托与Lambda表达式

    NET中那些所谓的新语法之三:系统预定义委托与Lambda表达式   开篇:在上一篇中,我们了解了匿名类.匿名方法与扩展方法等所谓的新语法,这一篇我们继续征程,看看系统预定义委托(Action/Fun ...

  6. 十二、C# 委托与Lambda表达式(匿名方法的另一种写法)

    委托与Lambda表达式   1.委托概述 2.匿名方法 3.语句Lambda 4.表达式Lambda 5.表达式树   一.委托概述 相当于C++当中的方法指针,在C#中使用delegate 委托来 ...

  7. C# Note2:委托(delegate) & Lambda表达式 & 事件(event)

    前言 本文主要讲述委托和Lambda表达式的基础知识,以及如何通过Lambda表达式实现委托调用,并阐述.NET如何将委托用作实现事件的方式. 参考:C#高级编程 1.什么是委托(delegate)? ...

  8. 使用Java函数接口及lambda表达式隔离和模拟外部依赖更容易滴单测

    概述 单测是提升软件质量的有力手段.然而,由于编程语言上的支持不力,以及一些不好的编程习惯,导致编写单测很困难. 最容易理解最容易编写的单测,莫过于独立函数的单测.所谓独立函数,就是只依赖于传入的参数 ...

  9. 委托学习过程及委托、Lambda表达式和匿名方法的关系总结及事件总结

    第一章,当开始学习委托的时候,我们会问什么是委托?为什么要学习委托? 一,什么是委托? 委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法, ...

随机推荐

  1. Binary Tree Maximum Path Sum [leetcode] dp

    a(i):在节点i由于单边路径的最大结束 b(i):在节点i路径和 a(i) = max{ i->val, i->val + max{a(i->left), a(i->righ ...

  2. PS多形式的部分之间复制“笨办法”

    PS剪切页面,有时候你可能会遇到这样的情况:设计改进,但是,我们要具有相同的切片. 在此假设,可以直接用于切割片.我们可以节省大量的时间,又分为片. 但是,人们一般不会在你的上跨片设计PSD在变化,但 ...

  3. python 时间处理

    在实践中,时间处理遇到的问题,需要 Sep 06, 2014 19:30 (UTC 时间) 和 比较当前时间,早晚.知道 此 2014-09-06 19:30 格时间表达式.因此,在处理,通缉 先将s ...

  4. Flex在使用无线电的button切换直方图横坐标和叙述性说明

    1.问题叙述性说明 一组单选button,有周和月之分,选择"周",柱状图横坐标显示的是周,纵坐标显示的是人数:选择"月",柱状图横坐标显示的月,纵坐标显示的是 ...

  5. hadoop 2.2.0集群安装

    相关阅读: hbase 0.98.1集群安装 本文将基于hadoop 2.2.0解说其在linux集群上的安装方法,并对一些重要的设置项进行解释,本文原文链接:http://blog.csdn.net ...

  6. C语言库函数大全及应用实例四

    原文:C语言库函数大全及应用实例四                                    [编程资料]C语言库函数大全及应用实例四 couble fmod (double x, dou ...

  7. SSH六部曲

    <strong> 一共有6步(文章底部附有源码下载地址,刚学完ssh的可以借鉴)</strong> 1 写一个Hibernate应用,完成用户的增加 1) User实体 2)U ...

  8. SQL Server中存储过程比直接运行SQL语句慢的原因

    原文:SQL Server中存储过程比直接运行SQL语句慢的原因 在很多的资料中都描述说SQLSERVER的存储过程较普通的SQL语句有以下优点: 1.       存储过程只在创造时进行编译即可,以 ...

  9. 从久负盛名的GoDaddy开发革命来看Node.js的风靡程度

    英文原文连接:http://venturebeat.com/2015/02/09/godaddy-nodejitsu/ 网站主机托管公司GoDaddy将要进一步通过新的开发工具来提升自身能力.最新消息 ...

  10. ORA-00265: instance recovery required, cannot set ARCHIVELOG mode

    症状: 我打开后归档命令报告运行错误ORA-00265 SQL> alter database archivelog; alter database archivelog * ERROR at ...