Почему в C # нет ReverseEnumerator?

Кто-нибудь знает, была ли конкретная причина или дизайнерское решение не включать обратный перечислитель в C #? Было бы так хорошо, если бы существовал эквивалент C ++ reverse_iterator, точно так же, как Enumerator является эквивалентом C ++ iterator. Коллекции, которые могут быть подвергнуты обратной итерации, просто реализуют что-то вроде IReverseEnumerable, и можно делать что-то вроде:

List<int>.ReverseEnumerator ritr = collection.GetReverseEnumerator();
while(rtir.MoveNext())
{
 // do stuff
}

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


person Rado    schedule 17.02.2012    source источник
comment
Интересный вопрос, MoveNext() мог бы показаться оксюмороном для обратного перечислителя, хотя я предполагаю, что для совместимости с foreach метод GetReverseEnumerator() все равно должен будет возвращать IEnumerator и, следовательно, потребует этого метода, даже если MovePrevious() будет казаться мне более подходящим   -  person RobV    schedule 18.02.2012
comment
Есть метод расширения Reverse linq, который достигает чего-то похожего ...   -  person RQDQ    schedule 18.02.2012
comment
Для типов, реализующих IList<T>, мы получаем эффективный Reverse от linq. К сожалению, не каждый тип, который может эффективно реализовать Reverse, может также реализовать IList<T>. Например, двусвязный список. Так что я согласен, что эти коллекции, имеющие встроенный метод Reverse(), были бы хороши.   -  person CodesInChaos    schedule 18.02.2012
comment
@RobV, MoveNext () будет иметь ту же концепцию, что и оператор приращения ++ в C ++, как если бы вы выполняли итерацию от rbegin () к rend (). Я не думал о совместимости с foreach ... это было бы слишком запутанно. Может быть, вместо этого есть другая конструкция foreachreverse   -  person Rado    schedule 18.02.2012
comment
@RQDQ, разве Reverse linq не буферизует результаты Enumerator, а затем выполняет перечисление в обратном порядке, тем самым используя больше места и дважды повторяя цикл?   -  person Rado    schedule 18.02.2012
comment
@CodeInChaos, на самом деле нет, Enumerable.Reverse() создает копию всей последовательности, даже для IList<T>. См. Джон Блог Скита.   -  person svick    schedule 18.02.2012
comment
@svick, упомянутое сообщение в блоге перемещено, теперь оно здесь.   -  person bdrajer    schedule 03.04.2017


Ответы (4)


Это было бы вполне возможно реализовать. Лично я почти никогда не выполняю обратных итераций. Если мне нужно это сделать, я сначала вызываю .Reverse (). Наверное, так думали и дизайнеры .NET BCL.

По умолчанию все функции не реализованы. Они должны быть спроектированы, реализованы, протестированы, задокументированы и поддержаны. - Раймонд Чен

Вот почему вы не реализуете функции, которые мало полезны. Вы начинаете с наиболее важных функций (таких как итерация от начала до конца). И вы останавливаетесь там, где либо ваш бюджет исчерпан, либо там, где, по вашему мнению, нет смысла продолжать.

Есть много вещей, которых нет в библиотеке базовых классов .NET. До .NET 4 не было даже File.EnumerateLines. И рискну сказать, что для большинства людей такая функциональность важнее, чем обратная итерация.

Возможно, вы работаете в бизнес-сфере, где обычна обратная итерация. Мой опыт противоположен. Как разработчик фреймворка вы можете только догадываться, кто будет использовать ваш фреймворк и какие функции потребуются этим людям. Трудно провести черту.

person usr    schedule 17.02.2012
comment
Разве это не называется File.ReadLines? - person nawfal; 15.12.2013

Он недоступен, потому что IEnumerator - итератор только вперед. У него есть только MoveNext() метод. Это делает интерфейс универсальным и составляет основу Linq. Существует множество реальных коллекций, которые нельзя повторять в обратном порядке, потому что для этого требуется хранилище. Например, большинство стримов такие.

Linq предоставляет решение с помощью метода расширения Reverse(). Он работает, сначала сохраняя элементы, а затем повторяя их в обратном порядке. Однако это может быть очень расточительным, для этого требуется O (n) хранилища. Отсутствует возможная оптимизация для коллекций, которые уже индексируются. Что можно исправить:

static class Extensions {
    public static IEnumerable<T> ReverseEx<T>(this IEnumerable<T> coll) {
        var quick = coll as IList<T>;
        if (quick == null) {
            foreach (T item in coll.Reverse()) yield return item;
        }
        else {
            for (int ix = quick.Count - 1; ix >= 0; --ix) {
                yield return quick[ix];
            }
        }
    }
}

Пример использования:

        var list = new List<int> { 0, 1, 2, 3 };
        foreach (var item in list.ReverseEx()) {
            Console.WriteLine(item);
        }

Вы захотите сделать специализацию для LinkedList, поскольку он не реализует IList<T>, но по-прежнему позволяет выполнять быструю обратную итерацию через свойства Last и LinkedListNode.Previous. Хотя гораздо лучше не использовать этот класс, у него плохая локализация кеш-памяти ЦП. Всегда отдавайте предпочтение List<T>, если вам не нужны дешевые вставки. Это могло выглядеть так:

    public static IEnumerable<T> ReverseEx<T>(this LinkedList<T> list) {
        var node = list.Last;
        while (node != null) {
            yield return node.Value;
            node = node.Previous;
        }
    }
person Hans Passant    schedule 18.02.2012
comment
Спасибо, Ганс, отличный код! Если вы хотите, вы можете обновить свой код, добавив последний: IReadOnlyList. - person Eric Ouellet; 11.05.2016

Подсказка находится в последней строке OP: используя это в списках и LinkedLists.

Итак, для List это подойдет:

    public static IEnumerable<T> AsReverseEnumerator<T>(this IReadOnlyList<T> list)
    {
        for (int i = list.Count; --i >= 0;) yield return list[i];
    }

Использование IReadOnlyList дает большую гибкость с точки зрения того, над чем он будет работать.

Нечто подобное возможно для LinkedLists.

person dsz    schedule 08.08.2019

Вопрос касается счетчика, а не перечисления. Если вы должны вернуть IEnumerator (например, при привязке к пользовательскому объекту источника данных в wpf / uwp / winui), вы можете сделать это следующим образом:

var reverseList = myList.AsEnumerable().Reverse().ToList();

return reverseList.GetEnumerator()

Уродливый, запутанный и рабочий, то есть полностью соответствующий требованиям Framework Guidelines.

person Michael B    schedule 23.02.2021