4.1 理解函数式编程
函数式编程与其他编程方法的区别就在于函数式编程不会修改数据或状态。函数式编程适用于深度学习、机器学习、人工智能等需要在同一个数据集上执行不同操作的场景。
.NET Framework中的LINQ语法就是一个函数式编程的例子。因此就算不清楚函数式编程的样子,但如果之前使用过LINQ,其实就已经接触了函数式编程,那就是它的模样。
函数式编程是一个深奥的主题。很多的书籍、课程和视频都对其进行了介绍。本章中我们只会从纯函数和不可变数据两个方面对其进行简要介绍。
纯函数只能操作传递给它的数据。因此这种方法是可预测且不会产生副作用的。这对程序员是非常有利的,因为这种方法更容易理解和测试。
不可变的数据对象或数据结构体中的数据一旦初始化就不会被更改。由于数据一旦设置就不会更改,因此很容易推断数据值、数据是如何设置的,以及对给定输入的各种操作的结果。由于输入明确而输出可预测,因此不可变数据也更容易测试。同时,由于不需要考虑诸如对象状态等其他情况,因此编写测试用例也更容易。不可变对象和结构体的另一个好处是它是线程安全的。线程安全的对象和结构体特别适合作为数据传递对象(Data Transfer Objects,DTO)在线程间进行传递。
但是结构体若包含引用类型,则仍然是能够更改的。为了确保它是不可变的,则需要确保引用类型也是不可变的。C# 7.2添加了readonly struct
和不可变数据结构支持。因此,即使结构体包含引用类型,也可以使用C# 7.2的功能确保包含引用类型的结构体是不可变的。
我们来看一个纯函数的范例。以下范例中,Player
类中的属性只能通过构造器在构造时进行赋值。该类唯一的职责就是保存玩家的名称及其最高得分。类中的定义了一个方法来更新玩家的最高分数:
请注意UpdateHighScore
方法,该方法不会更新HighScore
属性的值,而是使用了当前类中已经设置了好的PlayerName
变量以及highScore
参数,重新创建并返回了新的Player
类的实例。这就是在编程中不更改对象状态的简单范例。
函数式编程是一个很大的主题。其思想对于面向过程的程序员和面向对象的程序员来说需要比较艰难的转变才能适应。深入介绍函数式编程主题已经超出了本书的范畴。如需了解更多信息请参考PacktPub上众多函数式编程的资源。
Packt有众多函数式编程相关的书籍和视频。本章最后参考资料中包含一些Packet函数式编程资源链接。
LINQ是C#函数编程的典范。因此在继续介绍后续内容之前,我们先来观察一些LINQ范例。在介绍范例之前需要先准备数据集。以下代码创建了一系列供应商和商品列表。首先介绍Product
结构体:
定义好结构体之后,我们在GetProducts()
方法中添加一些范例数据:
最后我们在列表上使用LINQ。以下例子将列表中重复供应商去除并以供应商的名称排序,并打印最终结果:
首先,调用GetProducts()
方法并仅仅选择Vendor
列来获得供应商列表。接下来调用Distinct()
方法过滤列表确保不会包含重复的供应商。最后调用OrderBy(x => x)
方法将供应商名称按字母顺序排序(其中x
就是供应商名称)。在获得排好序的独立供应商列表之后,开始进行迭代并打印供应商的名称。最后,等待用户按键并退出程序。
函数编程中的方法比其他编程类型的方法要短小得多,这是函数编程的好处之一。接下来我们来讨论保持方法短小的好处,以及保持方法短小的技术手段(包括函数式编程)。