【问题标题】:LINQ: Not Any vs All Don'tLINQ:不是任何与所有都不是
【发布时间】:2012-02-20 02:26:51
【问题描述】:

我经常想检查提供的值是否与列表中的值匹配(例如在验证时):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

最近,我注意到 ReSharper 要求我将这些查询简化为:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

显然,这在逻辑上是相同的,也许更易读(如果你做了很多数学),我的问题是:这会导致性能下降吗?

感觉应该(即.Any() 听起来像是短路,而.All() 听起来不像),但我没有什么可以证实这一点。有没有人更深入地了解查询是否会解决相同的问题,或者 ReSharper 是否让我误入歧途?

【问题讨论】:

  • 你试过反汇编 Linq 代码看看它在做什么吗?
  • 在这种情况下,我实际上会选择 if(!acceptedValues.Contains(someValue)),但当然这不是问题 :)
  • @csgero 我同意。以上是对真实逻辑的简化(也许是过度简化)。
  • “感觉应该(即 .Any() 听起来像是短路,而 .All() 听起来却没有)”——不适合任何有直觉的人。您注意到的逻辑等价意味着它们同样可以短路。想了想,只要遇到不合格的情况,所有人都可以退出。
  • 我并不普遍同意 ReSharper 的观点。写出明智的思路。如果您想在缺少必需项的情况下引发异常:if (!sequence.Any(v => v == true))。如果您希望仅在一切都符合特定规范的情况下继续:if (sequence.All(v => v < 10))

标签: c# .net performance linq resharper


【解决方案1】:

All在第一次不匹配时短路,所以这不是问题。

一个微妙的领域是

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

是真的。序列中的所有项目都是偶数。

有关此方法的更多信息,请参阅Enumerable.All 的文档。

【讨论】:

  • 是的,但bool allEven = !Enumerable.Empty&lt;int&gt;().Any(i =&gt; i % 2 != 0) 也是如此。
  • @Jon 语义上没有!=全部。所以从语义上讲,你要么没有,要么没有,但在 .All() 的情况下,没有只是所有返回 true 的所有集合的子集,如果你不知道这种差异可能会导致错误。为那个安东尼 +1
  • @RuneFS 我不关注。从语义和逻辑上讲,“没有不真实的地方……”确实与“所有真实的地方”相同。例如。 “哪里没有我们公司接受的项目?”总是会得到与“其他公司接受的所有项目在哪里?”相同的答案...
  • ... 现在,假设“所有项目都是...”意味着至少有一个项目是至少一个满足测试的项目,您确实会遇到错误,因为“所有的项目......”对于空集总是正确的,我一点也不争辩。我补充说,虽然假设“没有任何项目......”意味着至少有一个项目不符合测试,但同样的问题可能会发生,因为“没有任何项目......”对于空集也总是正确的.并不是我不同意 Anthony 的观点,而是我认为它也适用于正在讨论的两个结构中的另一个。
  • @Jon 你说的是逻辑,我说的是语言学。人脑不能处理负面的(在处理正面之前它可以否定它),所以从这个意义上说,两者之间存在很大差异。这不会使您提出的逻辑不正确
【解决方案2】:

两者将具有相同的性能,因为在可以确定结果后两者都停止枚举 - Any() 在传递的谓词计算为 true 的第一项上和 All() 在谓词计算为 false 的第一项上。

【讨论】:

    【解决方案3】:

    根据 ILSpy 实现All(正如我实际上去查看的那样,而不是“好吧,那个方法有点像......”如果我们讨论的是理论而不是影响,我可能会这样做) .

    public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        if (source == null)
        {
            throw Error.ArgumentNull("source");
        }
        if (predicate == null)
        {
            throw Error.ArgumentNull("predicate");
        }
        foreach (TSource current in source)
        {
            if (!predicate(current))
            {
                return false;
            }
        }
        return true;
    }
    

    根据ILSpy实现Any

    public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        if (source == null)
        {
            throw Error.ArgumentNull("source");
        }
        if (predicate == null)
        {
            throw Error.ArgumentNull("predicate");
        }
        foreach (TSource current in source)
        {
            if (predicate(current))
            {
                return true;
            }
        }
        return false;
    }
    

    当然,生成的 IL 可能存在一些细微差别。但是不,不,没有。 IL 几乎相同,但在谓词匹配时返回 true 与在谓词不匹配时返回 false 明显相反。

    当然,这只是 linq-for-objects。有可能其他一些 linq 提供者对其中一个的处理比另一个要好得多,但如果是这样的话,哪个获得了更优化的实现几乎是随机的。

    这条规则似乎完全归结为某人认为if(determineSomethingTrue)if(!determineSomethingFalse) 更简单、更易读。公平地说,我认为他们有点意思,我经常发现if(!someTest) 令人困惑*,当有另一种相同冗长和复杂性的测试将在我们想要采取行动的条件下返回 true 时。然而,实际上,我个人认为没有什么比你给出的两种选择中的另一种更受欢迎的了,如果谓词更复杂,我可能会稍微倾向于前者。

    *不是像我不明白那样令人困惑,而是像我担心这个决定有一些我不明白的微妙原因一样令人困惑,并且需要一些精神上的跳跃才能意识到“不,他们只是决定这样做,等一下我再看这段代码是为了什么?...”

    【讨论】:

    • 我不确定行后做了什么,但对我来说更具可读性的是:if (not any) 比 if (all not equal)。
    • 当你的枚举没有值时会有很大的不同。 'Any' 总是返回 FALSE,而 'All' 总是返回 TRUE。所以说比一个是另一个的逻辑等价物并不完全正确!
    • @Arnaud Any 将返回 false,因此 !Any 将返回 true,因此它们是相同的。
    • @Arnaud 没有人评论说 Any 和 All 在逻辑上是等价的。或者换句话说,所有发表评论的人都没有说 Any 和 All 在逻辑上是等价的。等价于 !Any(predicate) 和 All(!predicate)。
    • @MacsDickinson 这根本没有区别,因为您没有比较相反的谓词。使用All!test.Any(x =&gt; x.Key == 3 &amp;&amp; x.Value == 1) 等价于test.All(x =&gt; !(x.Key == 3 &amp;&amp; x.Value == 1))(实际上等价于test.All(x =&gt; x.Key != 3 || x.Value != 1))。
    【解决方案4】:

    您可能会发现这些扩展方法使您的代码更具可读性:

    public static bool None<TSource>(this IEnumerable<TSource> source)
    {
        return !source.Any();
    }
    
    public static bool None<TSource>(this IEnumerable<TSource> source, 
                                     Func<TSource, bool> predicate)
    {
        return !source.Any(predicate);
    }
    

    现在代替原来的

    if (!acceptedValues.Any(v => v == someValue))
    {
        // exception logic
    }
    

    你可以说

    if (acceptedValues.None(v => v == someValue))
    {
        // exception logic
    }
    

    【讨论】:

    • 谢谢 - 我已经在考虑在我们的公共库中实现这些,但我还没有决定这是否是一个好主意。我同意它们使代码更具可读性,但我担心它们没有增加足够的价值。
    • 我找了无,但没有找到。它更具可读性。
    • 我必须添加空检查:return source == null || !source.Any(谓词);
    【解决方案5】:

    据此link

    任何 - 检查至少一个匹配项

    All – 检查是否全部匹配

    【讨论】:

    • 你是对的,但是对于给定的集合,它们同时停止。条件失败时的所有中断和与谓词匹配时的任何中断。所以技术上除了场景上没有什么不同
    【解决方案6】:

    All() 确定序列的所有元素是否满足条件。
    Any() 确定序列的任何元素是否满足条件。

    var numbers = new[]{1,2,3};
    
    numbers.All(n => n % 2 == 0); // returns false
    numbers.Any(n => n % 2 == 0); // returns true
    

    【讨论】:

      【解决方案7】:

      正如其他答案所涵盖的那样:这与性能无关,而与清晰度有关。

      您的两种选择都得到广泛支持:

      if (!acceptedValues.Any(v => v == someValue))
      {
          // exception logic
      }
      
      if (acceptedValues.All(v => v != someValue))
      {
          // exception logic
      }
      

      但我认为这可能会获得更广泛的支持

      var isValueAccepted = acceptedValues.Any(v => v == someValue);
      if (!isValueAccepted)
      {
          // exception logic
      }
      

      在否定任何东西之前简单地计算布尔值(并命名它)在我的脑海中清除了很多。

      【讨论】:

      • 在评估条件时,我完全支持名副其实的“解释变量”,并经常在我的代码中使用它们,但有时我仍然会在代码审查中遇到阻力,因为它添加了更多“不必要”的行给会员。但是,在您的情况下,我会否定解释变量,以便您可以阅读 if (valueIsNotAccepted) { ... } 以摆脱括号中的 ! 否定。
      【解决方案8】:

      如果您查看Enumerable source,您会发现AnyAll 的实现非常接近:

      public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
          if (source == null) throw Error.ArgumentNull("source");
          if (predicate == null) throw Error.ArgumentNull("predicate");
          foreach (TSource element in source) {
              if (predicate(element)) return true;
          }
          return false;
      }
      
      public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
          if (source == null) throw Error.ArgumentNull("source");
          if (predicate == null) throw Error.ArgumentNull("predicate");
          foreach (TSource element in source) {
              if (!predicate(element)) return false;
          }
          return true;
      }
      

      一种方法不可能比另一种方法明显快,因为唯一的区别在于布尔否定,因此更喜欢可读性而不是错误的性能获胜。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-05
        • 2010-12-03
        相关资源
        最近更新 更多