【问题标题】:IF implementations in C# [closed]C# 中的 IF 实现 [关闭]
【发布时间】:2014-12-26 02:33:46
【问题描述】:

大部分开发者写IF语句的方式如下

if (condition)
{
    //Do something here
}

当然这被认为是正常的,但通常会创建不那么优雅且有点丑陋的嵌套代码。 所以问题是:是否可以将传统的IF 语句转换为功能语句?

** 这是一个关于产生更具可读性代码的可能方法的开放问题,我不希望接受任何答案。我相信更好的人为自己选择最适合他们的解决方案并为他们选择的答案投票。

【问题讨论】:

  • 函数式 if 语句?您能否展示一个您尝试在 c# 中重现的其他语言的示例?
  • 我也不明白你所说的“功能性”是什么意思。 “功能陈述”听起来像是矛盾的说法。
  • 我认为最大的挑战是找到一种比嵌套 if 语句产生更好可读代码的方法。恕我直言,这两个答案(到目前为止)并没有这样做。我曾经用流畅的 API 编写了某种决策树。我稍后会尝试查看它(如果我仍然可以找到它)并回复您。
  • @MarcinJuraszek;很抱歉,但我没有其他语言的,并且假设这个问题是针对 C# 的。 @molbdnilo;你能给我一个更好的标题吗? @Gert Arnold 邀请您发布答案!我很高兴看到你的决策树:)

标签: c# linq if-statement functional-programming


【解决方案1】:

虽然我已经添加了关于“功能 if”又名条件表达式的响应。似乎需要更多的东西,那就是促进决策树。我在回复中提到使用 monads 是最好的方法。这是它是如何完成的。如果您之前没有使用过 monad,那么这可能看起来像是巫术,但它本质上是一种更好的方式来执行 @Gert Arnold 发布的内容。

我将采用 Gert 的基于客户适用性的决策树的想法。我将使用 LINQ,而不是使用带有 lambda 的流利风格来做出决定。这是 C# 对 monad 的原生支持。使用代码的结果将如下所示:

        var client = new Client { 
            CriminalRecord = false, 
            UsesCreditCard = true, 
            YearsInJob = 10, 
            Income = 70000 
        };

        var decision = from reliable in ClientDecisions.Reliable
                       from wealthy in ClientDecisions.Wealthy
                       select "They're reliable AND wealthy";

        var result = decision(client);
        if (result.HasValue) Console.WriteLine(result.Value);

        decision = from reliableOrWealthy in Decision.Either(
                       ClientDecisions.Reliable,
                       ClientDecisions.Wealthy
                       )
                   from stable in ClientDecisions.Stable
                   select "They're reliable OR wealthy, AND stable";

        result = decision(client);
        if (result.HasValue) Console.WriteLine(result.Value);

        decision = from reliable in ClientDecisions.Reliable
                   from wealthy in ClientDecisions.Wealthy
                   from stable in ClientDecisions.Stable
                   select "They're reliable AND wealthy, AND stable";

        result = decision(client);
        if (result.HasValue) Console.WriteLine(result.Value);

这种方法的美妙之处在于它是完全可组合的。您可以使用小的“决策部分”并将它们组合起来以做出更大的决策。完全不需要if

在上面的代码中,您将看到ClientDecisions 静态类的用法。这包含一些可重复使用的决策部分:

public static class ClientDecisions
{
    public static Decision<Client, Client> Reliable =>
        from client in Decision.Ask<Client>()
        where !client.CriminalRecord && client.UsesCreditCard
        select client;

    public static Decision<Client, Client> Wealthy =>
        from client in Decision.Ask<Client>()
        where client.Income > 100000
        select client;

    public static Decision<Client, Client> Stable =>
        from client in Decision.Ask<Client>()
        where client.YearsInJob > 2
        select client;
}

注意这不是使用IEnumerableIQueryable。在 C# 中,可以为任何类型提供 SelectSelectManyWhere 方法,如果实现正确,您可以将任何类型变成一元类型。

有趣的是,在这种情况下,我将把一个委托变成一个 monad。这样做的原因是因为我们需要一个值传递到计算中。这是委托的定义:

public delegate DecisionResult<O> Decision<I,O>(I input);

以上示例中的输入为Client,输出为string。但请注意,上面的委托返回DecisionResult&lt;O&gt;,而不是O(即string)。这是因为我们通过计算传播了一个名为HasValue 的属性。如果HasValue 在任何时候是false,则计算结束。这使我们能够在计算期间做出决定,从而阻止其余的计算执行。这是DecisionResult&lt;T&gt; 类:

public struct DecisionResult<T>
{
    private readonly T value;
    public readonly bool HasValue;

    internal DecisionResult(bool hasValue, T value = default(T))
    {
        this.value = value;
        HasValue = hasValue;
    }

    public T Value =>
        HasValue
            ? value
            : throw new DecisionFailedException();
}

接下来,我们将添加几个帮助方法来生成 DecisionResult&lt;T&gt; 值。稍后将在SelectSelectManyWhere 方法中使用它们。

public static class DecisionResult
{
    public static DecisionResult<O> Nothing<O>() =>
        new DecisionResult<O>(false);

    public static DecisionResult<O> Return<O>(O value) =>
        new DecisionResult<O>(true,value);
}

现在我们有了核心类型,我们可以将 SelectSelectManyWhere 扩展方法写入 Decision&lt;I,O&gt; 委托(是的,您可以为委托编写扩展方法!)

public static class Decision
{
    public static Decision<I, V> Select<I, U, V>(this Decision<I, U> self, Func<U, V> map) =>
        input =>
        {
            var res = self(input);
            return res.HasValue
                ? DecisionResult.Return(map(res.Value))
                : DecisionResult.Nothing<V>();
        };

    public static Decision<I, V> SelectMany<I, T, U, V>(
        this Decision<I, T> self,
        Func<T, Decision<I, U>> select,
        Func<T, U, V> project) =>
        input =>
        {
            var resT = self(input);
            if (resT.HasValue)
            {
                var resU = select(resT.Value)(input);
                return resU.HasValue
                    ? DecisionResult.Return(project(resT.Value, resU.Value))
                    : DecisionResult.Nothing<V>();
            }
            else
            {
                return DecisionResult.Nothing<V>();
            }
        };

    public static Decision<I, O> Where<I, O>(this Decision<I, O> self, Predicate<O> pred) =>
        input =>
        {
            var res = self(input);
            return res.HasValue
                ? pred(res.Value)
                    ? DecisionResult.Return(res.Value)
                    : DecisionResult.Nothing<O>()
                : DecisionResult.Nothing<O>();
        };
}

这些方法允许在 LINQ 表达式中使用 Decision&lt;I,O&gt;。我们需要在Decision 类中添加几个额外的方法。第一个是Ask&lt;I&gt;,它只是获取传递给计算的值并允许在表达式中使用它。之所以将其命名为 Ask,是因为该 monad 与 Haskell 的标准 Reader monad 非常相似,并且按照约定称为 ask

public static class Decision
{
    public static Decision<I,I> Ask<I>() =>
        input => DecisionResult.Return(input);
}

我们还希望允许条件操作,因此我们还将Either&lt;I,O&gt;(...) 添加到Decision。这需要任意数量的Decision&lt;I,O&gt; monad,一个接一个地遍历它们,如果成功则立即返回结果,否则继续。如果没有一个计算成功,那么整个计算将结束。这允许“或”类型行为和“切换”样式行为:

public static class Decision
{
    public static Decision<I, O> Either<I, O>( params Decision<I, O>[] decisions ) =>
        input =>
        {
            foreach(var decision in decisions)
            {
                var res = decision(input);
                if( res.HasValue )
                {
                    return res;
                }
            }
            return DecisionResult.Nothing<O>();
        };
}

我们不需要“逻辑与”,因为它可以实现为一系列from 表达式或where 表达式。

最后我们需要一个异常类型,以防程序员在.HasValue == false 时尝试在DecisionResult&lt;T&gt; 中使用.Value

public class DecisionFailedException : Exception
{
    public DecisionFailedException()
        :
        base("The decision wasn't made, and therefore doesn't have a value.")
    {
    }
}

使用这种技术(并基于扩展方法),您可以完全避免使用if。这是一种真正实用的技术。当然,这不是惯用的,但它是 C# 中最具声明性的方式。

【讨论】:

  • 出色的实现! +1
  • 谢谢 :) 我做了一点改进。将 DecisionResult 更改为一个结构,因此它更有可能被 CLR 保留在堆栈中,因此会减少 GC 压力。
  • 这是一份不错的工作。我喜欢您展示如何实现 LINQ 方法来完成自定义工作的方式。仍然......它有很多代码,生成一个评估一个对象的决策树。这就是我对“我的”决策树的困扰。有些决策需要客户和银行账户,有些需要客户和地理记录等。在我第一次尝试使用这棵树时,我发现自己创建了 DTO,它携带了遍历一棵树所需的所有信息。树中的更改需要修改后的 DTO。代码太容易坏了。最后的决定是:放弃它。
  • 一旦编写了Decision monad,您就不需要创建任何更专业的monad。它适用于任何类型,甚至适用于匿名类型,因此您不必创建 DTO 类。我创建 ClientDecisions 类只是为了展示如何重用组件。这使得构建决策树具有适当的组合性。其他好处是您可以通过提供提供“决策”的接口来注入不同的“规则”。想象一下,如果 ClientDecisions 不是静态的,而是像上面的示例一样被传递给使用它的函数。
  • (最后一条评论空间不足)- 如果您需要在整个树中更改输入值,则可以修改上述代码使其更像 State monad 而不是 Reader monad。因此,您不仅可以调用 Decision.Ask() 来获取输入,还可以调用 Decision.Put() 。在此处查看有关 Reader 和 State monad 有何不同的信息:github.com/louthy/csharp-monad
【解决方案2】:

以下技巧接受一个数字并打印一个字符串。可以修改为采用回调而不是简单的字符串。

//input: 1
//output: one

//input: 2
//output: two

//input: 3
//output: three

Func<string, bool> pr = s => 
{
    Console.WriteLine(s);
    return true;
};

string cr = Console.ReadLine();
int x = int.Parse(cr);

bool d = ((x == 1 && pr("one")) |
    (x==2 && pr("two")) |
    (x==3 && pr("three")));

回调版本:

Action<string> printSomething = s => Console.WriteLine(s);

Func<string, Action<string>, bool> pr = (s, a) => 
{
    a(s);
    return true;
};

string cr = Console.ReadLine();
int x = int.Parse(cr);

bool d = ((x == 1 && pr("one", printSomething)) |
    (x==2 && pr("two", printSomething)) |
    (x==3 && pr("three", printSomething)));

【讨论】:

    【解决方案3】:

    已经有一个“功能 if”又名“条件表达式”:

    bool value = true;
    
    var result = value 
                 ? "Yes"
                 : "No";
    

    也可以在LINQ中使用

    var q = from a in things
            select a > 10
                   ? "Greater than 10"
                   : "Less than or equal to 10";
    

    或者:

    var q = from a in things
            let r = a > 10 
                    ? "Greater than 10"
                    : "Less than or equal to 10"
            select r;
    

    http://msdn.microsoft.com/en-us/library/aa691313(v=vs.71).aspx

    【讨论】:

    • 很好,你是对的 +1,但我认为这种方法对决策树并没有真正的帮助。对吗?
    • 对不起,我在回答原来的问题:“是否可以将传统的 IF 语句转换为函数式的?”。 C# 中的标准 if/else 构造是基于语句的,因此不起作用。为了使某物变得实用,它应该是一种表达方式;也就是说,“then”和“else”分支都应该返回一个相同类型的值,然后将其用作表达式的结果。它是 C# 中为数不多的真正实用的组件之一。其他方法可能是使用 monad 来控制流程。我有一个完整的图书馆:github.com/louthy/csharp-monad
    【解决方案4】:

    我曾经编写了一个由决策节点组成的决策树。这里分享的代码太多了,我什至不确定我是否可以这样做,但我会展示 API 的样子:

    private readonly DecisionNode<Client> _isReliableTree =
        DecisionNode.Create<Client>("Criminal record", client => client.CriminalRecord)
            .WhenTrue(false)
            .WhenFalse(DecisionNode.Create<Client>("Credit card", 
                                                   client => client.UsesCreditCard)
               .WhenFalse(DecisionNode.Create<Client>("More than $40k", 
                                                      client => client.Income > 40000)
                  .WhenFalse(DecisionNode.Create<Client>("More than 2 years in job", 
                                                         client => client.YearsInJob > 2))));
    

    这将构建一个决策树,通过该决策树可以评估 Client 对象的可靠性(或可信度)。关键是WhenFalseWhenTrue 方法返回一个新的决策节点,该节点可以与新决策链接或评估为truefalse。默认情况下,一个节点的计算结果为true,这就是为什么会有更多的WhenFalse 节点。

    一个典型的用法是

    [TestMethod]
    public void FullReliableClient_ShouldBeReliable()
    {
        Assert.IsTrue(this._isReliableTree.Evaluate(ReliableClient));
    }
    

    其中ReliableClientClient,没有犯罪记录并且至少满足其他条件之一。

    我不得不说,这是受我在一些开发者活动中听到的 Scott Allen 的一次演讲启发的。而且我看到互联网上有很多类似的举措(寻找“C# + 决策树”)。

    if-then-else 更好吗?我不这么认为。最后,我从未在生产代码中使用过它。大多数业务逻辑过于复杂,无法适应只评估一个对象的僵化决策树。而且 IMO 并没有使代码更好地可读或可维护。

    没有if-then-else 的完全不同的决策方法是state machine。如果应用得当,它们会非常强大。

    【讨论】:

      【解决方案5】:

      我想到了一种编写函数式If 语句的方法,它看起来比简单的If 更优雅

      简单的如果 - 否则:

      void Main()
      {
          (true && true).Then(() => Console.WriteLine("Printed out when condition == true"))
                        .Else(() => Console.WriteLine("Printed out when condition == false"));
      
          (true && false).Then(() => Console.WriteLine("Printed out when condition == true"))
                        .Else(() => Console.WriteLine("Printed out when condition == false"));
      
          Console.ReadKey();
      }
      
      public static class FunctionalExtensions
      {
          private static bool OnConditionExecute(Action doSomething)
          {
              doSomething();
              return true;
          }
      
          //This is executed when condition == true
          public static bool Then(this bool condition, Action doSomething)
          {
              return (condition) && OnConditionExecute(doSomething);
          }
      
          //This is executed when condition == false
          public static bool Else(this bool condition, Action doSomething)
          {
              return (!condition) && OnConditionExecute(doSomething);
          }
      }
      

      多重验证:

      以下代码解决了我首先提到的问题。它可以用多个嵌套的讨厌的If 来实现,但这种方法要好得多,并且证明了传统的If 语句并不适合任何地方!

      void Main()
      {
          Console.WriteLine(IsValid("hello"));
          Console.ReadKey();
      }
      
      public bool IsValid(string name)
      {
          Func<string, bool>[] rules = 
          {
              // Some whatever validation rules...
              n => string.IsNullOrEmpty(name),
              n => n.Length < 5 || n.Length > 50,
              n => n.Equals("hello")
          };
          return rules.All(r => r(name) == false);
      }
      

      【讨论】:

      • 这绝对是一个有趣的想法。但它有一种强迫的感觉,恕我直言。尤其是 Then()Else() 的方法体,其中命令式表达的逻辑可以正常工作(并且对调用者隐藏)。恕我直言,最大的问题是程序充满了条件,这种方法不仅会导致方法调用(对扩展方法),还会导致程序集中匿名类型的扩散,以及运行时实例化对象及其委托的扩散.这可能会使每个有条件的选择比命令式的选择慢多个数量级。
      • 这绝不会降低您拥有的嵌套级别;事实上,它极大地增加了任何可能存在的嵌套的复杂性(通过引入闭包语义和语法),而没有任何移除它的能力。
      • @JorgeCode 难道你不认为如果有一个干净、简单的替代if 语句,它已经被广泛使用并集成到现代编程语言中了吗?
      • @JorgeCode 所以您同意您的回答没有回答您自己的问题,存在重大问题,无法提供任何实际理由说明为什么它更好,但您只是在说它更好?跨度>
      • 在您的“多重验证”示例中,您根本不需要ifs:return !(string.IsNullOrEmpty(name) || name.Length &lt; 5 || name.Length &gt; 50 || name == "hello");。这段代码比你的版本更简单,我认为它也更具可读性。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-09
      • 1970-01-01
      • 2019-04-16
      • 2010-10-24
      • 2013-02-14
      相关资源
      最近更新 更多