更具体一点:一旦谓词对一个项目产生了真值,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时停止,但我不确定,所以如果有人能够对此有所了解,那么我们将不胜感激。
答案 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对象的情况下,采用谓词的Single
和SingleOrDefault
的重载不会停止在已识别的失败上。虽然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;
}