он делает то, что это самый быстрый способ сделать то, что он должен делать.
При использовании на 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
использует аналогичные подходы, но должен возвращать полный объект (возможно, его создание). Аналогично !All(inversePredicate)
имеет тенденцию быть в значительной степени наравне с Any(predicate)
согласно this answer.
Single
является исключением из этого
Обновления: следующий с этим момента больше не применяется к .N Co, которая изменила реализацию Single
.
Важно отметить, что в случае объектов 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
имеет ту же проблему.
Это относится только к линк-к-объектам, но остается .Where(predicate).Single()
, а не Single(predicate)
.
[Документация для 'Enumerable.Any()'] (https://msdn.microsoft.com/en-us/library/vstudio/bb337697%28v=vs.100%29.aspx) явно указывает ответ к этому: * Перечисление источника прекращается, как только можно определить результат. * –