Any()是否会成功停止?

时间:2015-05-15 12:37:27

标签: c# linq

更具体一点:一旦谓词对一个项目产生了真值,Linq扩展方法Any(IEnumerable collection, Func predicate)是否会停止检查集合中所有剩余的元素?

因为我不想花太多时间来弄清楚我是否需​​要做真正昂贵的部分:

if(lotsOfItems.Any(x => x.ID == target.ID))
    //do expensive calculation here

因此,如果Any始终检查来源中的所有项目,这可能会浪费时间,而不仅仅是:

var candidate = lotsOfItems.FirstOrDefault(x => x.ID == target.ID)
if(candicate != null)
     //do expensive calculation here

因为我很确定FirstOrDefault一旦得到结果就会返回,并且如果它在集合中找不到合适的条目,则只会继续遍历整个Enumerable

是否有人有关于Any的内部运作的信息,或者是否有人可以为此类决定提出解决方案?

另外,一位同事提出了类似的建议:

if(!lotsOfItems.All(x => x.ID != target.ID))

因为这应该在条件第一次返回false时停止,但我不确定,所以如果有人能够对此有所了解,那么我们将不胜感激。

5 个答案:

答案 0 :(得分:8)

我们从source code看到,

Any()

nested是确定序列的任何元素是否满足LINQ条件的最有效方法。

  

另外:一位同事提出了类似的内容

     

if(!lotsOfItems.All(x => x.ID!= target.ID))因为这应该是   一旦条件第一次返回false就停止但我没有   当然,如果有人能够对此有所了解的话   将不胜感激:>]

All()确定序列的所有元素是否满足条件。因此,只要可以确定结果,就会停止源的枚举。

附加说明:
如果您使用Linq对象,则上述情况属实。如果您使用Linq to Database,那么它将创建一个查询并将对数据库执行它。

答案 1 :(得分:3)

您可以自己测试一下:https://ideone.com/nIDKxr

public static IEnumerable<int> Tester()
{
    yield return 1;
    yield return 2;
    throw new Exception();
}

static void Main(string[] args)
{
    Console.WriteLine(Tester().Any(x => x == 1));
    Console.WriteLine(Tester().Any(x => x == 2));

    try
    {
        Console.WriteLine(Tester().Any(x => x == 3));
    }
    catch
    {
        Console.WriteLine("Error here");
    }
}

是的,它确实: - )

  

另外:一位同事提出了类似的内容

     

if(!lotsOfItems.All(x =&gt; x.ID!= target.ID))

     

因为这应该在条件第一次返回false时停止,但我不确定,所以如果有人能够对此有所了解,那么我们将不胜感激:&gt;]

使用相同的推理,All()可以继续,即使其中一个元素返回false :-)不,偶数All()编程正确: - )

答案 2 :(得分:2)

它可以做任何最快捷的方式来做它必须做的事情。

IEnumerable上使用时,这将是:

foreach(var item in source)
  if(predicate(item))
    return true;
return false;

或者对于没有采用谓词的变体:

using(var en = source.GetEnumerator())
  return en.MoveNext();

在数据库中运行时,它将类似于

SELECT EXISTS(SELECT null FROM [some table] WHERE [some where clause])

等等。如何执行将依赖于哪些索引可用于实现WHERE子句,因此它可以是快速索引查找,在找到的第一个匹配时中止全表扫描,或者索引查找后跟部分表扫描中止找到第一场比赛,视情况而定。

然而其他Linq提供商还有其他实施方案,但一般来说负责人都会尝试至少合理有效。

总之,你可以依赖它至少比调用FirstOrDefault更有效率,因为FirstOrDefault使用类似的方法,但必须返回一个完整的对象(可能构建它)。同样,根据this answer!All(inversePredicate)Any(predicate)几乎相同。

Single是此

的例外情况

更新:此时的以下内容不再适用于已更改Single实现的.NET Core。

重要的是要注意,在linq-to对象的情况下,采用谓词的SingleSingleOrDefault的重载不会停止在已识别的失败上。虽然Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)的明显方法是:

public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
  /* do null checks */
  using(var en = source.GetEnumerator())
    while(en.MoveNext())
    {
      var val = en.Current;
      if(predicate(val))
      {
        while(en.MoveNext())
          if(predicate(en.Current))
            throw new InvalidOperationException("too many matching items");
        return val;
      }
    }
  throw new InvalidOperationException("no matching items");
}

实际实现类似于:

public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    /* do null checks */
    var result = default(TSource);
    long tally = 0;
    for(var item in source)
        if(predicate(item))
        {
            result = item;
            checked{++tally;}
        }
    switch(tally)
    {
        case 0:
            throw new InvalidOperationException("no matching items");
        case 1:
            return result;
        default:
            throw new InvalidOperationException("too many matching items");
    }
}

现在,虽然成功的Single必须扫描所有内容,但这可能意味着不成功的Single比它需要的速度慢得多(甚至可能抛出未记录的错误),如果意外重复的原因是一个错误,它将项目复制到序列中 - 因此使它远大于它应该是的,然后应该帮助你找到问题的Single现在正在拖延。

SingleOrDefault也有同样的问题。

这仅适用于linq-to-objects,但.Where(predicate).Single()而不是Single(predicate)更安全。

答案 3 :(得分:1)

第一场比赛的任何停站。所有在第一次不匹配时停止。

我不知道文档是否保证,但由于兼容性原因,此行为现在已经有效地修复了。这也很有道理。

答案 4 :(得分:1)

是的,一旦谓词满足,它就会停止。这是通过RedGate Reflector的代码:

    [__DynamicallyInvokable]
    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 local in source)
        {
            if (predicate(local))
            {
                return true;
            }
        }
        return false;
    }