Получит ли C # пользу от различий между типами перечислителей, таких как итераторы C ++? - PullRequest
5 голосов
/ 28 октября 2009

Я думал о методе IEnumerator.Reset(). Я прочитал в документации MSDN, что это только для COM-взаимодействия. Как программист на С ++, мне кажется, что IEnumerator, который поддерживает Reset, - это то, что я бы назвал прямым итератором , тогда как IEnumerator, который не поддерживает Reset, на самом деле входной итератор .

Итак, первая часть моего вопроса: правильное ли это понимание?

Вторая часть моего вопроса: будет ли какая-то польза в C #, если будет проведено различие между входными итераторами и прямыми итераторами (или «перечислителями», если вы предпочитаете)? Разве это не поможет устранить некоторую путаницу между программистами, подобную той, которая содержится в этом ТАКом вопросе о клонировании итераторов ?

РЕДАКТИРОВАТЬ: Разъяснения для прямого и входного итераторов. Итератор ввода гарантирует, что вы можете перечислить элементы коллекции (или из функции генератора или входного потока) только один раз. Именно так работает IEnumerator в C #. Возможность перечисления во второй раз или нет, определяется тем, поддерживается ли Reset. Прямой итератор, не имеет этого ограничения. Вы можете перечислять членов так часто, как вы хотите.

Некоторые программисты на C # не понимают, почему IEnumerator нельзя надежно использовать в многопроходном алгоритме. Рассмотрим следующий случай:

void PrintContents(IEnumerator<int> xs)
{
  while (iter.MoveNext())
    Console.WriteLine(iter.Current); 
  iter.Reset();
  while (iter.MoveNext())
    Console.WriteLine(iter.Current); 
}

Если мы будем называть PrintContents в этом контексте, нет проблем:

List<int> ys = new List<int>() { 1, 2, 3 }
PrintContents(ys.GetEnumerator()); 

Однако посмотрите на следующее:

IEnumerable<int> GenerateInts() {   
  System.Random rnd = new System.Random();
  for (int i=0; i < 10; ++i)
    yield return Rnd.Next();
}

PrintContents(GenerateInts());

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

Ответы [ 5 ]

3 голосов
/ 28 октября 2009

Reset было большой ошибкой. Я звоню махинациям на Reset. На мой взгляд, правильный способ отразить различие между «прямыми итераторами» и «входными итераторами» в системе типов .NET - это различие между IEnumerable<T> и IEnumerator<T>.

См. Также этот ответ , где Эрик Липперт из Microsoft (в неофициальной ёмкости, без сомнения, моя точка зрения состоит только в том, что он имеет больше полномочий, чем я, чтобы утверждать, что это была ошибка дизайна ) делает подобное замечание в комментариях. Также смотрите его удивительный блог .

2 голосов
/ 28 октября 2009

Интересный вопрос. Мое мнение таково, что, конечно, C # принесет пользу . Однако добавить его будет нелегко.

Различие существует в C ++ из-за его гораздо более гибкой системы типов. В C # у вас нет надежного универсального способа клонирования объектов, который необходим для представления прямых итераторов (для поддержки многопроходной итерации). И, конечно, для того, чтобы это было действительно полезно, вам также потребуется поддержка двунаправленных и случайных итераторов / перечислителей. И чтобы все они работали гладко, вам действительно нужна какая-то форма утки, как в шаблонах C ++.

В конечном счете, сферы действия этих двух концепций различны.

В C ++ итераторы должны представлять все, что вам нужно знать о диапазоне значений. Учитывая пару итераторов, мне не нужен оригинальный контейнер. Я могу сортировать, я могу искать, я могу манипулировать и копировать элементы так, как мне нравится. Оригинальный контейнер вне изображения.

В C # перечислители не предназначены для того, чтобы делать так много. В конечном счете, они просто предназначены для того, чтобы вы могли последовательно проходить последовательность.

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

К сожалению.

1 голос
/ 12 мая 2015

.NET Framework получила бы огромную выгоду, если бы была возможность спросить IEnumerator<T> о том, какие способности он может поддерживать и какие обещания он может дать. Такие функции также были бы полезны в IEnumerable<T>, но возможность задавать вопросы перечислителю позволила бы коду, который может получать перечислитель от оболочек, таких как ReadOnlyCollection, использовать базовую коллекцию улучшенными способами без необходимости использования оболочки.

При наличии любого перечислителя для коллекции, который может быть перечислен полностью и не слишком велик, можно получить из него IEnumerable<T>, который всегда будет давать одинаковую последовательность элементов (в частности, набор оставшихся элементов). в перечислителе) путем считывания всего его содержимого в массив, удаления и удаления перечислителя и получения перечислителей из массива (используя его вместо исходного заброшенного перечислителя), оборачивая массив в ReadOnlyCollection<T> и возвращая , Хотя такой подход будет работать с любым перечисляемым набором, отвечающим вышеуказанным критериям, он будет ужасно неэффективным для большинства из них. Наличие средства для запроса перечислителя передать его оставшееся содержимое в неизменяемом IEnumerable<T> позволило бы многим типам перечислителей выполнять указанное действие гораздо более эффективно.

1 голос
/ 28 октября 2009

Исходя из перспективы C #:

Вы почти никогда не используете IEnumerator напрямую. Обычно вы делаете оператор foreach, который ожидает IEnumerable.

IEnumerable _myCollection;
...
foreach (var item in _myCollection) { /* Do something */ }

Ты тоже не обойдешься IEnumerator. Если вы хотите передать коллекцию, которая нуждается в итерации, вы передаете IEnumerable. Поскольку IEnumerable имеет единственную функцию, которая возвращает IEnumerator, ее можно использовать для многократного повторения коллекции (несколько проходов).

Нет необходимости в Reset() функции на IEnumerator, потому что если вы хотите начать все сначала, вы просто выбрасываете старую (сборку мусора) и получаете новую.

0 голосов
/ 28 октября 2009

Я так не думаю. Я бы назвал IEnumerable прямым итератором и входным итератором. Он не позволяет вам вернуться назад или изменить базовую коллекцию. С добавлением ключевого слова foreach итераторы в большинстве случаев становятся почти не продуманными.

Мнение: Разница между входными итераторами ( каждый получает ) и выходными итераторами ( что-то делает с каждым ) слишком тривиальна, чтобы оправдать добавление в каркас. Кроме того, чтобы сделать выходной итератор, вам необходимо передать делегат итератору. Итератор ввода кажется более естественным для программистов на C #.

Существует также IList<T>, если программист хочет произвольный доступ.

...