2015-05-15 2 views
4

Чтобы быть более конкретным: будет ли метод расширения Linq Any(IEnumerable collection, Func predicate) остановить проверку всех оставшихся элементов коллекций после того, как предикат дал true для элемента?Есть ли() остановить успех?

Потому что я не хочу тратить много времени, на выяснение того, если мне нужно сделать очень дорогие детали на всех:

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)) 

, поскольку это, как предполагается, чтобы остановить, как только условия возвращает ложь в первый раз, но я не уверен в том, что, так что если кто-либо мог пролить некоторый свет на это, и это будет оценено.

+3

[Документация для 'Enumerable.Any()'] (https://msdn.microsoft.com/en-us/library/vstudio/bb337697%28v=vs.100%29.aspx) явно указывает ответ к этому: * Перечисление источника прекращается, как только можно определить результат. * –

ответ

8

Как мы видим из source code, Да:

internal static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate) { 
      foreach (T element in source) { 
       if (predicate(element)) { 
        return true; // Attention to this line 
       } 
      } 
      return false; 
     } 

Any() является наиболее эффективным способом, чтобы определить, удовлетворяет ли какой-либо элемент последовательности условие с помощью LINQ.

также: коллега предложил что-то вдоль линий

если с этим предполагается остановиться, как только условия (lotsOfItems.All (х => x.ID = target.ID)!) возвращает ложь в первый раз, но я не уверен, на что, так что если кто-то может пролить некоторый свет на это, так что будет оценен:>]

All() определяет, удовлетворяют ли все элементы последовательности состояние. Таким образом, перечисление источника прекращается, как только результат может быть определен.

Дополнительное примечание:
выше верно, если вы используете Linq к объектам. Если вы используете Linq для базы данных, тогда он создаст запрос и выполнит его для базы данных.

1

Любые остановки в первом матче. Все остановки в первом матче.

Я не знаю, гарантирует ли документация, но это поведение теперь эффективно фиксируется на все время из-за соображений совместимости. Это также имеет смысл.

1

Да, он останавливается, когда предикат выполняется один раз.Вот код через RedGate Отражатель:

[__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; 
    } 
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"); 
    } 
} 

Да, это делает :-)

также: коллега предложил что-то вдоль линий от

если (! lotsOfItems.All (x => x.ID! = target.ID))

, поскольку это, как предполагается, чтобы остановить, как только условия возвращает ложь в первый раз, но я не уверен в том, что, так что если кто-то может пролить некоторый свет на это, а также будет понятно:>]

Используя те же рассуждения, All() может продолжаться, даже если один из элемента возвращает ложь :-) Нет, даже All() правильно запрограммирован :-)

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 использует аналогичные подходы, но должен возвращать полный объект (возможно, его создание). Аналогично !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).

Смежные вопросы