Почему в С# нет ReverseEnumerator?
Кто-нибудь знает, есть ли конкретная причина или дизайнерское решение, чтобы не включать обратный счетчик в С#? Было бы так приятно, если бы был эквивалент С++ reverse_iterator
, так как Enumerator является эквивалентом С++ iterator
. Коллекции, которые могут быть обратными итерациями, будут просто реализовывать что-то вроде IReverseEnumerable, и можно было бы сделать что-то вроде:
List<int>.ReverseEnumerator ritr = collection.GetReverseEnumerator();
while(rtir.MoveNext())
{
// do stuff
}
Таким образом, вы могли бы выполнять итерацию списков и LinkedLists таким же образом, а не использовать индексатор для одной и предыдущих ссылок для другого, тем самым обеспечивая лучшую абстракцию
Ответы
Ответ 1
Было бы вполне возможно реализовать это. Лично я почти никогда не обращаю назад. Если мне нужно это сделать, я сначала вызываю .Reverse(). Вероятно, это то, о чем подумали и дизайнеры .NET BCL.
По умолчанию все функции не реализованы. Они должны разрабатываться, внедряться, тестироваться, документироваться и поддерживаться. - Раймонд Чен
И вот почему вы не реализуете функции, которые мало полезны. Вы начинаете с самых важных функций (например, итерации спереди-назад). И вы останавливаетесь где-нибудь, где либо ваш бюджет исчерпан, либо где вы думаете, что нет смысла продолжать.
В библиотеке базового класса .NET есть много вещей. До .NET 4 даже не было File.EnumerateLines
. И я бы рискнул сказать, что такая функциональность важнее, чем обратная итерация для большинства людей.
Возможно, вы работаете в бизнес-домене, где обратная итерация распространена. Мой опыт противоположный. В качестве дизайнера фреймворков вы можете только догадываться, кто будет использовать вашу инфраструктуру и какие функции потребуют эти люди. Трудно провести линию.
Ответ 2
Он недоступен, потому что IEnumerable является только итератором только вперед. Он имеет только метод 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 < > , но все же позволяет ускорить обратную итерацию с помощью свойств Last и LinkedListNode.Previous. Хотя гораздо лучше не использовать этот класс, он имеет паршивость локального процессора. Всегда предпочитайте List < > , когда вам не нужны дешевые вставки. Это может выглядеть так:
public static IEnumerable<T> ReverseEx<T>(this LinkedList<T> list) {
var node = list.Last;
while (node != null) {
yield return node.Value;
node = node.Previous;
}
}
Ответ 3
Подсказка в заключительной строке OP: использование этого в списках и списках ссылок.
Итак, для List
этот будет хорошо работать:
public static IEnumerable<T> AsReverseEnumerator<T>(this IReadOnlyList<T> list)
{
for (int i = list.Count; --i >= 0;) yield return list[i];
}
Использование IReadOnlyList
дает большую гибкость с точки зрения того, над чем он будет работать.
Нечто подобное было бы возможно для LinkedLists
.
Ответ 4
Из документов: рекомендуется использовать foreach вместо прямого манипулирования перечислением (см. http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx). Итак, Тревор прав, вы можете просто сделать:
foreach(var el in collection.Reverse())
{
}
Функция Reverse()
не олицетворяет коллекцию, поэтому небольшие накладные расходы по сравнению с итерацией с обратной стороны.