Почему Single(IEnumerable‹T›,Predicate‹T›) настолько неэффективен

код из ссылок .Net

    public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
      if (source == null)
        throw Error.ArgumentNull("source");
      if (predicate == null)
        throw Error.ArgumentNull("predicate");
      TSource source1 = default (TSource);
      long num = 0L;
      foreach (TSource source2 in source)
      {
        if (predicate(source2))
        {
          source1 = source2;
          checked { ++num; }
        }
      }
      switch (num)
      {
        case 0L:
          throw Error.NoMatch();
        case 1L:
          return source1;
        default:
          throw Error.MoreThanOneMatch();
      }
    }

поэтому вопрос: почему бы не бросить исключение, только если есть более одного совпадающего элемента? Так, например, если у нас есть большая коллекция, у нас могут возникнуть большие проблемы с производительностью. Почему бы не написать простой if в коде? Пример:

public static class LinqTester
{
    public static TSource MySingle<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        if (source == null)
            throw new ArgumentNullException("source");
        if (predicate == null)
            throw new ArgumentNullException("predicate");
        TSource result = default(TSource);
        int count = 0;
        foreach (TSource value in source)
        {
            if (predicate(value))
            {
                checked { ++count; }
                if (count > 1)
                    throw new Exception("MoreThanOneMatch");
                result = value;
            }
        }
        if (count == 0)
            throw new Exception("NoMatch");
        return result;
    }
}

Тест:

    static void Main(string[] args)
    {
        var arr = new byte[100000000];

        var sw = Stopwatch.StartNew();
        try
        {
            Console.WriteLine(arr.Single(i => i == 0));
        }
        catch (Exception)
        {
            sw.Stop();
        }
        finally
        {
            Console.WriteLine(sw.Elapsed);
        }


        var sw2 = Stopwatch.StartNew();
        try
        {
            Console.WriteLine(arr.MySingle(i => i == 0));
        }
        catch (Exception)
        {
            sw2.Stop();
        }
        finally
        {
            Console.WriteLine(sw2.Elapsed);
        }

        Console.WriteLine("Difference = {0}", (double) sw.ElapsedTicks/sw2.ElapsedTicks);

    }

полученные результаты:

http://ideone.com/A62JOn

Я переписал оригинальный сингл, потому что моно имеет другую реализацию, всего в 3,5 раза медленнее.


person Alex Zhukovskiy    schedule 28.05.2014    source источник
comment
Каковы результаты ?   -  person Selman Genç    schedule 28.05.2014
comment
И когда вы публикуете свои результаты, пожалуйста, делайте это в режиме выпуска без прикрепленного отладчика.   -  person Scott Chamberlain    schedule 28.05.2014
comment
Запустил тест в Release без отладчика на моей машине: 798 мс для Single, 0 мс для MySingle.   -  person SuperOli    schedule 28.05.2014
comment
Я думаю, что на вопрос следует ответить, просто взглянув на код. Он может легко выйти из петли накоротко, но этого не происходит. Почему бы и нет?   -  person Chris    schedule 28.05.2014
comment
Подводя итог ответа в связанном дубликате. Выполнение этого таким образом оптимизирует случай успеха, когда Single() не вызовет исключение. Дизайнеры решили оптимизировать для успеха, а не для неудач.   -  person Scott Chamberlain    schedule 28.05.2014
comment
@ScottChamberlain Это не медленнее, чем простой Single(), потому что в обычном случае оператор if будет проверяться только один раз для одного совпадающего элемента. И непонятно, почему Single(IEnumerable<T>) повысил производительность, а Single(IEnumerable<T>,Predicate<T>) - нет.   -  person Alex Zhukovskiy    schedule 28.05.2014