我不确定是什么时候决定将 C# 打造成面向对象/函数式混合语言的。最早的基础工作是在 C# 3 中奠定的。当时引入了 Lambda 表达式和匿名类型等功能,这些功能后来成为 .NET 3.5 中 Linq 的一部分。
但在那之后,很长一段时间内,函数式功能方面并没有太多新东西。事实上,直到 2017 年 C# 7 发布后,函数式编程似乎才再次与 C# 团队相关。
从 C# 7 开始,每个版本的 C# 都包含一些新的和令人兴奋的东西,可以进行更多函数式编码,这一趋势目前没有任何停止的迹象!
在上一章中,我们研究了可以在几乎任何可能仍在使用的 C# 代码库中实现的函数式功能。在本章中,我们将抛弃这一假设,并研究如果您的代码库允许使用任何最新功能(或至少是自 C# 7 以来发布的功能),您可以使用的所有功能。
元组
元组是在 C#7 中引入的。Nuget 包确实存在,以允许一些旧版本的 C# 也使用它们。它们基本上是一种将属性的快速集合组合在一起的方法,而无需创建和维护类。
如果您有几个属性想要在一个地方保留一分钟,然后立即处理,那么元组非常适合。
如果您有多个要在 Select 之间传递的对象,或者有多个项目想要传入或传出,那么您可以使用元组。
这是您可能考虑使用元组进行以下操作的示例:
var filmIds = new[]
{
4665,
6718,
7101
};
// 将 filmIds 数组中的每个 int 元素
// 转换为包含影片和演员列表的元组
// 作为单独的属性
var filmsWithCast = filmIds.Select(x => (
film: GetFilm(x),
castList: GetCastList(x)
));
//这里的“x”是一个元组,现在它被转换为字符串
var renderedFilmDetails = filmsWithCast.Select(x =>
@$"
Title: {
x.film.Title}
Director: {
x.film.Director}
Cast: {
string.Join(", ", x.castList)}
".Trim());
在我上面的例子中,我使用 Tuple 将两个查找函数的数据与每个给定的电影 ID 配对,这意味着我可以运行后续的 Select 来将对象对简化为单个返回值。
模式匹配
Switch 语句出现的时间比现在仍在工作的开发人员的时间还要长。它们有自己的用途,但其功能非常有限。函数式编程采用了这一概念并将其提升了几个层次。这就是模式匹配。
C# 7 开始将此功能引入 C# 语言,随后在后续版本中多次增强,并且很可能将来还会添加更多功能。
模式匹配是一种节省大量工作的神奇方法。为了向您展示我的意思,我现在将向您展示一些过程代码,然后展示如何在几个不同版本的 C# 中实现模式匹配。
程序化银行账户
为了举个例子,让我们想象一下经典的面向对象工作示例之一——银行账户。我将创建一组银行账户类型,每个账户都有不同的计算利息金额的规则。这些并不是真正基于真实的银行业务,它们完全出自我的想象。
这些是我的规则:
- 标准银行账户通过将余额乘以账户利率来计算利息
- 余额为 10,000 或更少的高级银行账户是标准银行账户
- 余额超过 10,000 的高级银行账户适用的利率加上额外奖励利率
- 百万富翁的银行账户,拥有的钱多得超过了小数所能容纳的最大值(这是一个非常非常大的数字 - 大约 8*10^28,所以他们一定非常富有。如果我问他/她,你认为他/她愿意借给我一点吗?我可以买一双新鞋)。他们有一个溢出余额属性,用于添加他们拥有的所有超过最大小数值的钱,他们不能像我们这些平民一样将其存储在标准余额属性中。他们需要根据两个余额来计算利息。
- 大富翁玩家的银行账户。他们通过考试可以额外获得 200 美元。我没有实施“直接进监狱”的逻辑,一天只有这么多小时。
这些是我的课程:
public class StandardBankAccount
{
public decimal Balance {
get; set; }
public decimal InterestRate {
get; set; }
}
public class PremiumBankAccount : StandardBankAccount
{
public decimal BonusInterestRate {
get; set; }
}
public class MillionairesBankAccount : StandardBankAccount
{
public decimal OverflowBalance {
get; set; }
}
public class MonopolyPlayersBankAccount : StandardBankAccount
{
public decimal PassingGoBonus {
get; set; }
}
面向对象的方法来实现这些规则 - 或者我认为是“长手”方法,可能看起来像这样:
public decimal CalculateInterest(StandardBankAccount sba)
{
// If real type of object is PremiumBankAccount
if (sba.GetType() == typeof(PremiumBankAccount))
{
// cast to correct type so we can access the Bonus interest
var pba = (PremiumBankAccount)sba;
if (pba.Balance > 10000)
{
return pba.Balance * (pba.InterestRate + pba.BonusInterestRate);
}
}
// if real type of object is a Millionaire's bank account
if(sba.GetType() == typeof(MillionairesBankAccount))
{
// cast to the correct type so we can get access to the overflow
var mba = (MillionairesBankAccount)sba;
return (mba.Balance * mba.InterestRate) +
(mba.OverflowBalance * mba.InterestRate)
}
// if real type of object is a Monopoly Player's bank account
if(sba.GetType() == typeof(MonopolyPlayersBankAccount))
{
// cast to the correct type so we can get access to the bonus
var mba = (MonopolyPlayersBankAccount)sba