寻找替代的LINQ表达式

时间:2018-07-12 12:38:56

标签: c# linq

我正在开发一个代码生成器,该代码生成器根据某些业务规则验证对象。举个例子,我很好奇地发现逻辑下面可以用多种方式写成LINQ表达式。

当collection为null或“ TrueAndCorrect”项的计数不是1时,断言应评估为true。一种可能的解决方案是:

bool assertion = report.DeclarationOfTrusteeCollection == null 
|| report.DeclarationOfTrusteeCollection.Count(f => f.FTER99.Equals("TrueAndCorrect")) != 1

是否可以使用Any,取反运算符或任何其他方式将LINQ表示为更紧凑的其他方式?

2 个答案:

答案 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;

两个版本都只会迭代直到找到第二个匹配项为止(如果没有找到匹配项,则直到结束为止)。