Объяснение дизайна итераторов C# (по сравнению с C++)

Я нашел подобную тему:
Итераторы в C++ (stl) по сравнению с Java, там концептуальное различие?

Который в основном имеет дело с итератором Java (который подобен C#), неспособность к движению назад.

Таким образом, здесь я хотел бы сфокусироваться на пределах - в итераторе C++, не знает его предела, Вы имеете собой, сравнивают данный итератор с пределом. В итераторе C# знает более - можно сказать без по сравнению с любой внешней ссылкой, если итератор допустим или нет.

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

Но конечно MS знал это и C++ при разработке C#. Таким образом, каковы преимущества пути C#? Какой подход более мощен (который приводит к более изящным функциям на основе итераторов). Что я пропускаю?

Если у Вас есть мысли о C# по сравнению с дизайном итераторов C++ кроме их пределов (границы), также ответьте.

Примечание: (на всякий случай) сохраните обсуждение строго техническим. Никакой C++/C# flamewar.

РЕДАКТИРОВАНИЯ

Как Tzaman выразился "нет никакого преимущества для наличия предела быть выраженным отдельно, потому что нет никакого способа добраться там кроме обхода одного элемента за один раз". Однако не то, чтобы трудный к созданному итератор C#, который делает несколько шагов время, таким образом вопрос - является там преимуществом наличия явного предельного итератора (как в C++)? Если да - что?

@Jon,

Edit1: скажем, у Вас есть функциональное нечто, которое делает что-то на итераторах (пример очень наивен!)

void foo(iter_from,iter_end) // C++ style
void foo(iter) // C# style 

И теперь требуется назвать функциональную панель на всех элементах кроме последних 10.

bar(iter_from,iter_end-10); // C++ style

В C# (если бы я не ошибаюсь) необходимо было бы предоставить дополнительный метод для этого итератора для изменения его предела, чего-то вроде этого:

bar(iter.ChangeTheLimit(-10));

Edit2: После перечитывания Вашего сообщения я обнаруживаю решающее различие. В C++ Вы работаете над итераторами набора, в C# Вы работаете над наборами (итератор используется "внутренне"). Если да, я все еще чувствую себя немного неловко из-за C# - Вы выполняете итерации набора и когда Вы находите интересный элемент, требуется передать все элементы от "сюда" для окончания. В C++ это очень легко и нет никаких издержек. В C# Вы или передаете итератор или набор (если последний, там будут дополнительные вычисления). Я буду ожидать Вашего комментария :-)

@Hans,

Я не сравниваю яблоки и апельсины. Теория аккомпанемента является общим заземлением здесь, таким образом, у Вас есть алгоритм сортировки, раздел, и т.д. У Вас есть понятие набора (или последовательность, как Jon нравится). Теперь - вопрос - то, как Вы разрабатываете доступ к элементам, чтобы записать изящный алгоритм в C# или C++ (или любой другой язык). Я хотел бы понять обоснование, "мы сделали это, потому что...".

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

sort(IEnumerable coll) 

функция вместо этого

sort(IEnumerator iter)

корректный? Так в этом смысле я предполагаю, что Вы не можете взять итераторы C# в качестве итераторов C++, потому что C# не выражает тех же алгоритмов тот же путь, как C++ делает. Или поскольку Jon указал - в C#, Вы скорее преобразовываете набор (Пропуск, Возьмите) вместо того, чтобы изменить итераторы.

7
задан Hans Passant 25 October 2018 в 14:57
поделиться

4 ответа

Вы сравниваете яблоки и апельсины. Классы коллекций STL преследовали совершенно разные цели проектирования. Они представляют собой универсальный дизайн для коллекций, вы можете получить только те классы, которые предоставляются. Невозможно настроить коллекцию, классы STL не предназначены для наследования.

В отличие от .NET, их ядром являются интерфейсы ICollection, IDictionary и IList. Платформа .NET содержит сотни классов коллекций, настроенных для выполнения своей конкретной работы.

Это также повлияло на конструкцию их итераторов. Итераторы STL должны обеспечивать большую гибкость, поскольку коллекции, которые они итерируют, не могут быть настроены. С этим связана неотъемлемая цена. Существует 5 различных типов итераторов STL, не все классы коллекций поддерживают все 5 разных типов. Потеря общности никогда не бывает хорошей идеей в библиотечном дизайне.

Хотя можно утверждать, что итераторы C ++ более гибкие, я сделаю противоположное утверждение. Простота IEnumerator позволила реализовать очень ценные функции .NET. Например, ключевое слово yield в C # полностью отделяет коллекцию от поведения итератора. Linq не было бы, если бы не простой тип итератора. Поддержка ковариации была бы невозможна.

Что касается вашего Edit2 , нет, итераторы .NET - это отдельные классы, как и классы C ++. Убедитесь, что вы понимаете разницу между IEnumerable (реализуется коллекцией) и IEnumerator (реализуется итератором).

2
ответ дан 6 December 2019 в 11:45
поделиться

Так в чем же преимущества C #?

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

Пример C ++:

std::vector<int> a;
std::vector<int> b;
std::vector<int>::iterator ba = a.begin();
std::vector<int>::iterator ea = a.end();
std::vector<int>::iterator bb = b.begin();
std::vector<int>::iterator eb = b.end();
// lots of code here
for(auto itr = ba; itr != eb; itr++) // iterator tries to "cross" vectors
                                     // you get undefined behavior, depending 
                                     // on what you do with itr

Какой подход более эффективен (какой приводит к более элегантным функциям, основанным на на итераторах).

Это зависит от того, что вы подразумеваете под самым могущественным.

Подход C ++ более гибкий.

С C # труднее злоупотреблять.

3
ответ дан 6 December 2019 в 11:45
поделиться

Я думаю, что пока вы говорите только об итераторах forward , мне нравится способ C # - последовательность знает свой собственный предел, вы можете легко преобразовать ее через LINQ и так далее. Нет никакой пользы в том, чтобы ограничение было выражено отдельно, потому что нет никакого способа добраться до , кроме как пройти по одному элементу за раз.

Однако итераторы C ++ намного мощнее - вы можете иметь двунаправленные итераторы или итераторы с произвольным доступом, которые позволяют делать то, что невозможно с односторонним последовательным итератором. Последовательный итератор в некотором смысле является обобщением связанного списка , а итератор произвольного доступа является обобщением массива . Вот почему вы можете эффективно выражать алгоритмы, такие как быстрая сортировка, в терминах итераторов C ++, в то время как это было бы невозможно с IEnumerable .

6
ответ дан 6 December 2019 в 11:45
поделиться

Мне кажется, что дизайн C# более инкапсулирован: последовательность заканчивается или выполняется, не завися ни от чего другого. Где имеет смысл сравнивать одно ограничение с другим?

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

foo.Take(10)
foo.Skip(10)
foo.Where(x => x.StartsWith("y"))

etc

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

EDIT: Чтобы ответить на ваш вопрос, отредактируйте его: в C# (по крайней мере, в LINQ) вы не будете изменять существующую коллекцию. Вы создадите новую последовательность из старой. Это делается лениво; не создается новая копия или что-то в этом роде. Для LINQ to Objects это выполняется с помощью методов расширения IEnumerable, поэтому любая последовательность получает те же возможности.

Обратите внимание, что это не ограничивается традиционными коллекциями - это может быть последовательность строк, считанных из файла журнала (опять же, лениво). Вам не нужно знать саму коллекцию, только то, что это последовательность, из которой можно брать элементы. Есть также разница между IEnumerable и IEnumerator, где первый представляет последовательность, а второй - итератор по последовательности; IEnumerator редко используется явно в C#, или передается по кругу.

Теперь, ваш пример "все элементы, кроме последних 10" является сложным, потому что для общей последовательности вы не можете сказать, что вы находитесь в 10 элементах от конца, пока не достигнете конца. В LINQ to Objects нет ничего, чтобы сделать это в явном виде. Для чего-либо, реализующего ICollection или ICollection, можно использовать

Bar(list.Take(list.Count - 10))

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

6
ответ дан 6 December 2019 в 11:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: