C# 中的 IF 实现

本文关键字:实现 IF 中的 | 更新日期: 2023-09-27 18:28:19

大多数开发人员通过以下方式编写IF语句

if (condition)
{
    //Do something here
}

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

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

C# 中的 IF 实现

尽管我已经添加了有关"函数式 if"又名条件表达式的响应。 似乎还有更多要求,那就是促进决策树。 我在答复中提到使用monads是最好的方法。 这是它是如何完成的。 如果你以前没有使用过monads,那么这可能看起来像巫毒教,但它本质上是做@Gert Arnold发布的更好方法。

我将采用 Gert 关于基于客户适合性的决策树的想法。 我将使用 LINQ,而不是使用 lambda 的流畅样式来做决策。 这是C#对monads的原生支持。 使用代码的结果将如下所示:

        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 方法提供方法,如果实现正确,则可以将任何类型转换为 monadic 类型。

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

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

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

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<T>值的帮助程序方法。 它们将在稍后的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<I,O>委托(是的,你可以为委托编写扩展方法!

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<I,O>的方法。 我们将在 Decision 类中需要几个额外的方法。 第一个是Ask<I>,它只是获取传递给计算的值,并允许它在表达式中使用。 它之所以被命名为Ask,是因为这个monad与Haskell的标准Reader monad非常相似,并且按照惯例称为ask

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

希望允许条件操作,因此我们还将Either<I,O>(...)添加到Decision。 这需要任意数量的Decision<I,O> monads,一个接一个地运行它们,如果成功,它会立即返回结果,如果没有,它会继续。 如果所有计算都不成功,则整个计算将结束。 这允许"或"类型行为和"切换"样式行为:

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<T>中使用.Value

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

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

已经有一个"函数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

我曾经编写过一个由决策节点组成的决策树。在这里分享的代码太多了,我什至不确定我是否可以做到这一点,但我会展示 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));
}

ReliableClient是没有犯罪记录且至少符合树其他标准之一的Client

我不得不说,这是受到斯科特·艾伦(Scott Allen(的演讲的启发,我曾经在一些开发人员的活动中听到过。而且我已经看到互联网上有很多类似的举措(查找"C# +决策树"(。

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

一种完全不同的无if-then-else决策方法是状态机。如果应用得当,这些可以非常强大。

我虽然是一种编写函数式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);
}

以下技巧采用数字并打印字符串。可以对其进行修改以采用回调而不是简单的字符串。

//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)));