C# 中的 IF 实现
本文关键字:实现 IF 中的 | 更新日期: 2023-09-27 18:28:19
大多数开发人员通过以下方式编写IF
语句
if (condition)
{
//Do something here
}
当然,这被认为是正常的,但通常可以创建嵌套代码,这些代码不是那么优雅且有点丑陋。那么问题来了:是否可以将传统的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;
}
注意:这不是使用 IEnumerable
或 IQueryable
。 在 C# 中,可以为任何类型的 Select
、 SelectMany
和 Where
方法提供方法,如果实现正确,则可以将任何类型转换为 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>
值的帮助程序方法。 它们将在稍后的Select
、SelectMany
和Where
方法中使用。
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);
}
现在我们有了核心类型,我们可以将Select
、SelectMany
和Where
扩展方法写入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
对象的可靠性(或可信度(。关键是WhenFalse
或WhenTrue
方法返回一个新的决策节点,该节点可以与新决策链接,也可以计算为 true
或 false
。默认情况下,节点的计算结果为 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)));