IF实现在C#中

时间:2014-10-30 06:28:56

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

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

if (condition)
{
    //Do something here
}

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

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

5 个答案:

答案 0 :(得分:8)

虽然我已经添加了关于'功能性的回复如果'又称条件表达式。似乎有更多的东西被要求,那就是促进决策树。我提到使用monads作为答复中的最佳方法。这是它的完成方式。如果你之前没有使用monad,这可能看起来像伏都教,但它本质上是做出@Gert Arnold发布的更好的方式。

我会根据客户的适合性来考虑Gert对决策树的看法。不使用lambdas做出决定的流畅风格,我将使用LINQ。这是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方法,如果正确实现,则允许您将任何类型转换为monadic类型。

有趣的是,在这个例子中,我将把一个代表变成一个monad。这是因为我们需要一个值传递给计算。这是委托的定义:

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

上面示例中的输入为Client,输出为string。但请注意,上面的代理人返回DecisionResult<O>,而不是O(这将是string)。这是因为我们在计算中传播了一个名为HasValue的属性。如果HasValue在任何时候都是false,则计算结束。这允许我们在计算期间做出决定,从而停止执行其余的计算。这是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> monad,逐个遍历它们,如果一个成功,它会立即返回结果,如果没有继续。如果没有一个计算成功,那么整个计算将结束。这允许&#39;或&#39;类型行为,&#39;开关&#39;风格行为:

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>();
        };
}

我们不需要逻辑和&#39;,因为它可以实现为一系列from表达式或where表达式。

最后,如果程序员在.Value时尝试在DecisionResult<T>中使用.HasValue == false,我们需要一个例外类型。

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

使用此技术(以及扩展方法为基础)可以避免完全使用if。这是一种真正的功能性技术。当然,这不是惯用语,但它是你在C#中找到的最具声明性的方式。

答案 1 :(得分:5)

已经存在'功能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

答案 2 :(得分:2)

我曾经编写了一个由决策节点组成的决策树。在这里分享的代码太多了,我甚至不确定我是否可以这样做,但我会展示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,没有犯罪记录,并且至少满足其他一项树标准。

我不得不说这是受到斯科特艾伦的一次谈话的启发,我曾经听过一些开发人员的讲话。事件。而且我已经看到互联网上有许多类似的举措(寻找&#34; C#+决策树&#34;)。

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

对于没有if-then-else的决策,完全不同的方法是state machine。如果应用得当,这些可能非常强大。

答案 3 :(得分:0)

我虽然可以编写功能性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);
}

答案 4 :(得分:0)

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

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