2016-11-11 3 views
4

В LINQ есть 2 способа подсчета числа: Count и LongCount. Практически единственная разница между этими двумя заключается в том, что первый возвращает int, а второй возвращает long.Есть ли практическая причина, по которой добавлен метод расширения LongCount от LINQ?

Непонятно, почему был добавлен второй метод. Кажется, что его единственным вариантом будет обработка перечислимых элементов из более чем 2B элементов. Это похоже на плохое решение для меня, по нескольким причинам:

  1. Большинство BCL коллекций подкреплены одномерными массивами, которые имеют длины, которые гарантированно подходит в int. Пытаясь пройти мимо, поднимет OverflowException/OutOfMemoryException.

  2. LongCount is O (n) since IEnumerable является ленивым. Если вы перечисляете элемент 3B, вы вызываете на него LongCount, а затем повторяете его снова (что вам нужно, если вы хотите использовать любое из значений), вы будете добавлять дополнительные 3B итерации, которые будут чрезвычайно медленным и скрывающим его от разработчика.

  3. Другие операции LINQ, такие как ToArray/ToList, не поддерживают перечисления с элементами 2B + из-за (1).

Am Я-то здесь отсутствует, или есть более практическая причина, почему LongCount был добавлен? Благодарю.

+0

На основе исходного кода 'LongCount' просто выполняет итерацию через' IEnumerable' с помощью 'Enumerator.MoveNext', в то время как' Count' пытается использовать 'IEnumerable' для' ICollection' и использовать '' Count'', если кастинг был неуспешен, он будет итерации 'IEnumerable' так же, как' LongCount'. [Https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,d76b4b5d3fd67767](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs, d76b4b5d3fd67767). Основываясь на этом «предположении» от @EricLipert, кажется очень логичным – Fabio

+0

@Fabio, это просто оптимизация. Было бы семантически корректно для 'LongCount' проверять также' ICollection' и просто приводить результат к 'long', но' LongCount' не используется так сильно, поэтому поэтому они не беспокоились об этом. –

ответ

4

У меня нет непосредственных знаний об этом дизайнерском решении, но я могу предложить обоснованное предположение.

Метод полезен для IQueryable; запрос может быть легко поддержан огромной таблицей базы данных.

Я ожидал бы

IQueryable<Foo> q = whatever; 
long result1 = q.LongCount(); 
long result2 = q.AsEnumerable().LongCount(); 

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

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

+1

Вчера я собирался ответить на что-то подобное до тех пор, пока не достиг реализации 'LongCount' в' Enumerable.cs'. Он просто перечисляет последовательность от начала до конца. Я думал, что собираюсь найти что-то вроде проверки, была ли реализация 'IEnumerable ' ICollection 'и играть с' Count' или кто знает что. –

+0

Даже если мы будем говорить о каком-то перечислимом, поддерживаемом сетевым потоком, в конце дня 'LongCount' будет идти один за другим, чтобы проверить, что * long count *. –

+0

@ MatíasFidemraizer: Это неверно. Чтобы расширить пример Эрик Липперт из огромной таблицы базы данных: запрос счетчика на базу данных очень часто избегает идти один за другим, вместо этого полагаясь на индексы и метаданные, чтобы вычислить счет в сублинейном времени. Менее ориентированным на базу данных примером будет отсортированный список произвольного доступа, содержащий дубликаты. Можно было бы вычислить счет в O (logn), запустив слегка измененный двоичный поиск. База данных может выполнять аналогичные оптимизации на основе индексов. – Brian

2

Я совершенно уверен, что она была введена для запросов к базе данных (например, он должен генерировать COUNT_BIG вместо COUNT для SQL сервера запросов), однако мощи есть некоторые использовать в других ситуациях. Например, предположим, что у меня есть такой метод:

private static Random _r = new Random(1); 
public static IEnumerable<BigInteger> RandomSequence(int upTo) 
{ 
    while (true) { 
     var next = _r.Next(); 
     if (next > upTo) 
      yield break; 
     yield return next; 
    } 
} 

Эта последовательность не выпекается никаким массивом и не хранит значения нигде. Таким образом, он может легко создавать более 2B предметов. Теперь предположим, что я хочу проверить, сколько итераций потребовалось бы, чтобы генерировать число больше, чем int.MaxValue - 5. Если бы я это сделать:

RandomSequence(int.MaxValue - 5).Count(); 

Он потерпит неудачу с переливом исключением (потому что Count удобно внутренне оборачивает приращение в checked области). Но LongCount на помощь!

RandomSequence(int.MaxValue - 5).LongCount(); 

Теперь я, наконец, понял, что с семенем 1 Random.Next будет производить больше, чем результат int.MaxValue - 5 в 2583066202 итераций!

Да, пример несколько убежден, но все же.

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