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

相信不少人都据说过函数式编程,提到函数式程序设计,脑海里涌现出来更多的是Lisp、Haskell等语言,而C#,彷佛咱们并不把它当成函数式语言,其实,函数式程序设计并非只针对某个特定的程序设计语言,而C#,也正一步步使用函数式丰富本身的语言结构,帮助人们更好的实现指望的结果。程序员

函数式程序设计算法

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

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

函数式程序设计属于一种定向思惟。若是咱们愿意按某种方式去思考,则它能够给咱们提供有趣的解决方案或者至少思考的源头,它们都与当前程序设计的许多实际问题有关。模块化

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

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

因为C#的函数只能出如今类中,所以它们一般被称为方法。方法能够接受若干个参数,而且能够有一个返回值。ui

与许多面向对象语言同样,C#类中的方法能够是实例方法,也能够是类方法。而在纯函数式程序设计中,没有类,也没有类的实例——固然,有不少方法保存数据,但一般不是用类来保存数据,它们老是在许多方面表现出不一样。this

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

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

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

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

DoSomething "string paramers"

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

SomeClass.DoSomething("string paramers");

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

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

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

 1         int Add(int x, int y)
 2         {
 3             return x + y;
 4         }
 5 
 6         int Add(int x, int y,int z)
 7         {
 8             return Add(x, y) + z;
 9         }
10 
11         double Add(double x, double y)
12         {
13             return x + y;
14         }
15 
16         double Add(double x, double y, double z)
17         {
18             return Add(x, y) + z;
19         }
View Code

在这个例子中,咱们很清楚地看出为何重载与重用有关:它容许程序员建立与原函数相似的新函数,同时尽量利用原函数已有的功能。

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

并不是全部的函数都重要到须要一个名称,通常而言,这些函数并非类级别的函数,它们没有名称,这些函数的引用地址保存在变量中,所以只要有这些函数的引用地址就能够调用它们。

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

1 static void AnonymousMethods()
2         {
3             BubbleSorter.IsAGeaterThanBDelegate compareInt =
4                 delegate(object a, object b)
5                 {
6                     return ((int)a) > ((int)b);
7                 };
8         }
View Code

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

1 BubbleSorter.IsAGeaterThanBDelegate compareInt2 =
2                 (object a, object b) => { return ((int)a) > ((int)b); };
View Code

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

1 BubbleSorter.IsAGeaterThanBDelegate compareInt2 =
2                 (a, b) => { return ((int)a) > ((int)b); };
View Code

其次,因为函数除了返回一个值外不执行任何操做,所以能够把函数体转换为表达式体,而且能够利用隐式返回:

BubbleSorter.IsAGeaterThanBDelegate compareInt2 =
                (a, b) =>(int)a) > ((int)b);
View Code

表达式体颇有用。由于有了它,在函数式程序中原本须要用函数实现的某个操做如今能够简化为一个表达式。与函数同样,表达式体也要接受参数并返回一个值。表达式体不能够包含任何与返回值求值无关的代码(即只要有一个返回值就行,遗憾的是,常常在表达式体中使用没有返回值的表达式)。

前面的例子若是使用其中一个泛型委托类型,就能够变成以下的形式:

1 Func<object,object,bool> compareInt3=
2             (a, b) => ((int)a) > ((int)b);
View Code

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

使用Lambda表达式时,有一个细节须要牢记:只有当全部类型都肯定后,编译器才会根据几个比较复杂的准则进行类型推断。编译器并非总能正确地推断出类型,所以,若是全部的类型都肯定了,编译器的要求就知足了:

1 Func<int, int, int> add =
2             (a, b) => a + b;
View Code

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

1 var add =
2             (a, b) => a + b;
View Code

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

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

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

 1 namespace CompanyWideTools
 2 {
 3     public static class StringHelper
 4     {
 5         public static string ConCat(this string[] strings, string separator)
 6         {
 7             bool first = true;
 8             var builder = new StringBuilder();
 9             foreach (var s in strings)
10             {
11                 if (!first)
12                     builder.Append(separator);
13                 else
14                     first = false;
15                 builder.Append(s);
16             }
17             return builder.ToString();
18         }
19     }
20 }
View Code

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

1         string[] strings = new[]
2             {
3                 "to","be","or","not","to","be"
4             };
5 
6             Console.WriteLine(StringHelper.ConCat(strings," "));    
View Code

然而,因为它是扩展方法,所以也能够像下面这样调用:

1  Console.WriteLine(strings.ConCat(""));
View Code

当咱们须要充分利用扩展方法的优势时,这种调用方法比较简单。

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

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

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

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

相关文章
相关标签/搜索