当前位置: 首页 > 使用教程

【Telerik UI for ASP.NET AJAX教程】C#中的函数编程

发布时间:2018-11-05

【下载Telerik UI for ASP.NET AJAX最新版本】

在面向对象编程(OOP)中,我们习惯于使用对象集合或简单数据类型。我们经常使用LINQ对这些集合进行排序和过滤,作为业务逻辑行为或数据转换的一部分。虽然这些是我们经常执行的有用任务,但很容易忘记C#中的函数可以被视为数据。如果我们重新考虑作为数据的函数思考,它使我们能够发现OOP中标准问题的替代解决方案。

在本文中,我们将看一下C#Functional Programming研讨会的一个例子。该场景概述了用于对扑克牌进行评分的解决方案。我们将研究一种利用函数作为数据的解决方案的替代模式。通过这种新模式,我们将为游戏的评分机制提供灵活性。

(一)评分标准

首先,让我们来看看用于产生最终得分的各个评分函数。每个功能都是一个规则,用于确定手牌是否符合标准。

private bool HasFlush(IEnumerable<Card> cards) => ...;
private bool HasRoyalFlush(IEnumerable<Card> cards) => ...;
private bool HasPair(IEnumerable<Card> cards) => ...;
private bool HasThreeOfAKind(IEnumerable<Card> cards) => ...;
private bool HasFourOfAKind(IEnumerable<Card> cards) => ...;
private bool HasFullHouse(IEnumerable<Card> cards) => ...;
private bool HasStraightFlush(IEnumerable<Card> cards) => ...;
private bool HasStraight(IEnumerable<Card> cards) => ...;

下图说明了游戏得分的规则。虽然这些函数表明手是否符合标准,但它们不会直接影响手的最终得分。我们需要安排规则并按重要性评估它们以产生分数并将其分配给HandRank的枚举型数据。

telerik

(二)确定分数

使用规则,我们可以通过几种不同的方式确定最终得分值。以下每个示例在技术上都是正确的,并提供其自身的可读性和简单性。每种方法的消极方面是规则执行的顺序是“hard coded”。

2.1维护状态

这种评估分数的方法使用临时占位符值来跟踪分数。每次评估都会进行,score并使用最好的HandRank进行更新。此方法非常明确,但涉及完成任务所不需要的额外代码和变量。

public HandRank GetScore(Hand hand)
{
    var score = HandRank.HighCard;
    if (HasPair(hand.Cards)) { score = HandRank.Pair; }
     ... 
    if (HasRoyalFlush(hand.Cards)) { score = HandRank.RoyalFlush; }
    return score;
}

2.2返回值

使用返回早期模式允许我们编写直观的代码,通过在发现评估为真时立即从函数返回来返回最佳HandRank。由于应用程序需要新规则,因此该方法易于阅读且易于修改。

public HandRank GetScore(Hand hand)
{
    if (HasRoyalFlush()) return HandRank.RoyalFlush;
     ...
    if (HasPair()) return HandRank.Pair;
    return HandRank.HighCard;
}

2.3三元表达式

可以使用三元运算符将函数写为单个表达式。这与返回早期方法具有类似的效果,但代码更少。对于某些人来说,这种方法的可读性可能比其他人更容易。

在所有前面的例子中,操作顺序是至关重要的。如果我们决定在此评分函数中添加新规则,那么我们需要确保以正确的顺序插入它们以确定正确的分数。

(二)思考功能

GetScore操作正逐步完成标准评估,并将结果为true的第一个规则与匹配的HandRank匹配。我们可以从函数式编程思维方式中解决问题,而不是将函数作为单独的语句进行评估。让我们通过将函数视为数据来改变我们对问题的看法。

如果我们将各个评分函数看作数据,我们就可以识别出一种模式。考虑以下评分函数的signature。

private bool HasFlush(IEnumerable<Card> cards) => ...;
private bool HasRoyalFlush(IEnumerable<Card> cards) => ...;
private bool HasPair(IEnumerable<Card> cards) => ...;
private bool HasThreeOfAKind(IEnumerable<Card> cards) => ...;
private bool HasFourOfAKind(IEnumerable<Card> cards) => ...;
private bool HasFullHouse(IEnumerable<Card> cards) => ...;
private bool HasStraightFlush(IEnumerable<Card> cards) => ...;
private bool HasStraight(IEnumerable<Card> cards) => ...;

每个功能都属于同一类型Func, bool>。由于我们有许多相同类型的数据,我们可以将它们安排在一个集合或数组中。接下来,我们需要将每个函数与它所代表的HandRank相匹配。例如:HasPair将得分为HandRank.Pair。使用元组,我们可以轻松创建此映射,而无需专门的类。在C#7.1中,我们可以通过简单地在括号中包含多个值来创建元组。使用函数及其映射的枚举器,我们可以构建集合。

private List<(Func<IEnumerable<Card>, bool> eval, HandRank rank)> GameRules() =>
   new List<(Func<IEnumerable<Card>, bool> eval, HandRank rank)>
   {
               (cards => HasRoyalFlush(cards), HandRank.RoyalFlush),
               (cards => HasStraightFlush(cards), HandRank.StraightFlush),
               (cards => HasFourOfAKind(cards), HandRank.FourOfAKind),
               (cards => HasFullHouse(cards), HandRank.FullHouse),
               (cards => HasFlush(cards), HandRank.Flush),
               (cards => HasStraight(cards), HandRank.Straight),
               (cards => HasThreeOfAKind(cards), HandRank.ThreeOfAKind),
               (cards => HasPair(cards), HandRank.Pair),
               (cards => true, HandRank.HighCard),
   };

为了保持代码整洁,我们将把集合的构造包装在一个名为GameRules的函数中。我们以后可以将其作为其他游戏规则的可扩展点。通过将排名系统移到GetScore方法之外,可以修改或替换新的评估和排名。对于可能的最低排名,我们将简单地使用true来表示默认评估。

(三)使用LINQ进行重构

现在我们将使用LINQ重写GetScore方法来评估列表。通过将列表中的项目视为数据,我们可以利用排序来确保它们以正确的顺序执行。我们不再需要担心“硬编码”执行顺序。我们可以使用.OrderByDescending(card => card.rank)将评估从最强等级排序到最弱,因为HandRank.RoyalFlush具有最高值。

public HandRank GetScore(Hand hand) => GameRules()
                    .OrderByDescending(rule => rule.rank)
                    .First(rule => rule.eval(hand.Cards)).rank;

最后,为了得到结果,我们将进行评估。最有效的方法是使用First LINQ方法。由于First是短路运算符,因此只要找到返回true的第一个项目,它就会停止对项目进行评估。当第一项计算结果为true时,我们将从数据集中获取元组的排名值并返回它。排名值是我们的最终得分。

(四)结论

C#中的函数通常被认为是静态语句,我们的应用程序可以使用它来更改系统中的数据状态。通过将我们的观点从命令性转变为功能性,我们可以找到替代解决方案。为问题带来功能性思维的一种方法是记住函数也是数据,并且符合许多与C#中其他数据类型相同的规则。在这个例子中,我们看到了一种功能方法如何将基于语句的硬编码评估转换为灵活的排序和基于地图的评估。这个简单的更改扩展了应用程序的功能,并在添加新条件时减少了摩擦,因为没有预定义操作顺序。

购买telerik正版授权的朋友可以点击"咨询在线客服"哦~~~