我正在开发一个代码生成器,该代码生成器根据某些业务规则验证对象。举个例子,我很好奇地发现逻辑下面可以用多种方式写成LINQ表达式。
当collection为null或“ TrueAndCorrect”项的计数不是1时,断言应评估为true。一种可能的解决方案是:
bool assertion = report.DeclarationOfTrusteeCollection == null
|| report.DeclarationOfTrusteeCollection.Count(f => f.FTER99.Equals("TrueAndCorrect")) != 1
是否可以使用Any,取反运算符或任何其他方式将LINQ表示为更紧凑的其他方式?
答案 0 :(得分:2)
原始代码是:
bool assertion =
report.DeclarationOfTrusteeCollection == null ||
report.DeclarationOfTrusteeCollection.Count(
f => f.FTER99.Equals("TrueAndCorrect")) != 1;
这里有些问题。
首先,空检查的意图似乎是“空集合与空集合具有相同的语义”。 这是C#中最糟糕的做法。永远不要这样做!如果要表示一个空集合,请创建一个空集合。您甚至可以使用Enumerable.Empty
个辅助方法。
因此,从此开始;该代码应为:
if (report.DeclarationOfTrusteeCollection == null)
throw some appropriate exception
或
Debug.Assert(report.DeclarationOfTrusteeCollection != null);
如果条件不可能的话。
这给我们留下了
bool assertion =
report.DeclarationOfTrusteeCollection.Count(
f => f.FTER99.Equals("TrueAndCorrect")) != 1;
这很糟糕。假设我向您展示了一个装有一些便士的罐子,然后问您“罐子中是否正好有一个便士?”在知道答案之前,您必须数几便士?您的代码在这里计数了全部 ,但是您可以在两点之后停止。
Enumerable
为您提供了一种方法,如果一个序列不是单例,则会抛出,但没有测试的方法。幸运的是,它很容易编写。 此处的最佳做法是编写一个具有所需语义的助手方法:
static class Extensions
{
public static bool IsSingleton<T>(this IEnumerable<T> items)
{
bool seenOne = false;
foreach(T item in items)
{
if (seenOne) return false;
seenOne = true;
}
return seenOne;
}
public static bool IsSingleton<T>(
this IEnumerable<T> items, Func<T, bool> predicate) =>
items.Where(predicate).IsSingleton();
}
完成。现在您的代码是:
if (report.DeclarationOfTrusteeCollection == null)
throw some appropriate exception
bool assertion =
report.DeclarationOfTrusteeCollection.IsSingleton(f => ...);
编写代码,使其读起来就像逻辑上所做的一样。这就是LINQ序列运算符的美丽和力量。
答案 1 :(得分:0)
您可以使用null传播运算符:
bool assertion = report.DeclarationOfTrusteeCollection?.Count(f => f.FTER99.Equals("TrueAndCorrect")) != 1;
由于null
不是1
,因此,如果集合是true
,则null
也是如此。
如果您不需要计算整个集合,那就太好了,因为您已经知道当有多个匹配元素时,这是错误的。但我不知道内置方法。您可以编写自己的扩展程序:
public static class MyExtensions
{
public static bool IsNullOrHasNotExactlyOneMatching<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null) return true;
bool found = false;
foreach(T element in source)
{
if (!predicate(element)) continue;
if (found) return true; // this is the second match!
found = true;
}
return !found; // one match found (or not)
}
}
并使用它:
bool assertion = report.DeclarationOfTrusteeCollection.IsNullOrHasNotExactlyOneMatching(f => f.FTER99.Equals("TrueAndCorrect"));
如Rawling所述,您可以使用Take()
来缩短扩展名:
public static bool IsNullOrHasNotExactlyOneMatching<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return source?.Where(predicate).Take(2).Count() != 1;
}
或直接执行以下操作:
bool assertion = report.DeclarationOfTrusteeCollection?.Where(f => f.FTER99.Equals("TrueAndCorrect"))
.Take(2).Count() != 1;
两个版本都只会迭代直到找到第二个匹配项为止(如果没有找到匹配项,则直到结束为止)。