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. OCP读书笔记(23) - 题库(ExamC)

    200.Which operation requires that you create an auxiliary instance manually before executing the ope ...

  2. Android利用网络编程HttpClient批量上传(两)AsyncTask+HttpClient监测进展情况,并上传

    请尊重别人的劳动.转载请注明出处: Android网络编程之使用HttpClient批量上传文件(二)AsyncTask+HttpClient并实现上传进度监听 执行效果图: 我曾在<Andro ...

  3. 现代JVM内存管理方法的发展历程,GC的实现及相关设计概述(转)

    JVM区域总体分两类,heap区和非heap区.heap区又分:Eden Space(伊甸园).Survivor Space(幸存者区).Tenured Gen(老年代-养老区). 非heap区又分: ...

  4. CQRS

    CQRS 2015-06-04 15:33 by 敏捷的水, 177 阅读, 0 评论, 收藏, 编辑 CQRS是Command Query Responsibility Seperation(命令查 ...

  5. Linq入门演练---(1)基本用法-分组,排序,内连接

    这一节大家共同学习下LINQ的基本用法,主要包括LINQ的分组,排序,和内外连接. 1.分组 基本语法:  group element by key element 表示查询结果返回的元素,key表示 ...

  6. NET使用了UpdatePanel后如何弹出对话框!

    原文:NET使用了UpdatePanel后如何弹出对话框! 在ajax中的UpdatePanel弹出对话窗,可以使用: ScriptManager.RegisterStartupScript(Upda ...

  7. Android研究之游戏开发处理按键的响应

    1.onKeyDown 方法 onKeyDown 方法是KeyEvent.Callback 接口中的一个抽象方法,重写onKeyDown 方法能够监听到按键被按下的事件,我们先看看onKeyDown方 ...

  8. EF4.1: Add/Attach and Entity States(EF中的实体状态转换说明)

    实体的状态,连接以及 SaveChanges 方法 数据库上下文对象维护内存中的对象与数据库中数据行之间的同步.这些信息在调用 SaveChanges方法被调用的时候使用.例如,当使用 Add 方法传 ...

  9. UML之轻松入门(2)-掌握Junit,让我们的开发更高效

         使用UML不仅能够形象化的表达我们的程序思想,并且能够帮助我们提高程序的质量.一个杂乱无章的程序让维护者望而生畏,其成本也可想而知.在面向程序设计(OOD)中有5条原则是帮助我们设计一个高效 ...

  10. windows+php5.5+apache2.4+tomcat+mod_jk配置

    原因: 通常情况下apache执行的是80port,比方apache启动后执行localhost:80就能够出现It works页面,这里的80也能够不写,会默认的.而tomcat启动时默认的port ...