Оптимизировать LINQ для IList - PullRequest
9 голосов
/ 03 мая 2011

Некоторое время назад я написал метод расширения IList для перечисления по части списка с использованием индексов. Во время рефакторинга я понял, что аналогичный запрос можно выполнить, вызвав Skip(toSkip).Take(amount). В процессе тестирования я заметил, что Skip не оптимизирован для IList. Немного погуглив, я оказался на посту Джона Скита, обсуждая, почему такие методы оптимизации, как Skip, опасны .

Насколько я понимаю в статье, проблема не в том, что исключение возникает в оптимизированных методах при изменении коллекции, но в комментарии говорится, что документация msdn сама конфликтует.

В IEnumerator.MoveNext () :

Если в коллекцию вносятся изменения, такие как добавление, изменение или удаление элементы, перечислитель безвозвратно признан недействительным и следующий вызов MoveNext или Сброс бросает InvalidOperationException.

В IEnumerator.GetEnumerator () :

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

Я вижу достоинства в обоих соглашениях, и немного растерялся, оптимизировать или нет. Что такое правильное решение? Я рассматривал подход IList.AssumeImmutable() в соответствии с AsParallel(), как упомянул Крис Вандермоттен в комментариях. Любая реализация уже существует или это плохая идея?

Ответы [ 2 ]

3 голосов
/ 03 мая 2011

Я согласен с Рэйфом, что неопределенное поведение более правильно. Только версионные коллекции могут генерировать исключения, и не все коллекции версионируются (самый большой пример - массивы). Даже версионные коллекции могут вести себя неправильно, если вы делаете ровно 2 ^ 32 изменения между вызовами на MoveNext.

Предполагая, что вы действительно заботитесь о поведении версий, решение состоит в том, чтобы получить Enumerator для IList и вызывать MoveNext для каждой итерации:

    public static IEnumerable<T> Skip<T>(this IList<T> source, int count)
    {
        using (var e = source.GetEnumerator())
            while (count < source.Count && e.MoveNext())
                yield return source[count++];
    }

Таким образом, вы получаете O (1) поведение путем индексации, но вы все равно получаете все поведение при вызове исключения MoveNext. Обратите внимание, что мы вызываем MoveNext только для побочных эффектов; мы игнорируем значения, по которым оно перечисляется.

0 голосов
/ 03 мая 2011

Класс ReadOnlyCollection может помочь с вашей неизменной коллекцией.

Мой совет: лично я бы не пытался «обмануть» компилятор, если у вас не возникли проблемы с производительностью. Вы никогда не знаете, что в следующей версии ваш оптимизированный код будет работать в два раза медленнее, чем оригинал. Не превентивно оптимизировать. Методы, представленные в платформе, могут создать действительно оптимизированный код, который будет трудно реализовать повторно.

здесь - это статья из MSDN, в которой содержится информация о том, какие коллекции использовать для различных целей. Я бы использовал соответствующую коллекцию для задачи вместо того, чтобы пытаться оптимизировать Skip and Take.

...