Обоснование разработки итераторов C # (по сравнению с C ++) - PullRequest
7 голосов
/ 14 июня 2010

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

Который в основном касается Java-итератора (который похож на C #), который не может вернуться назад.

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

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

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

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

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

редактирует

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

@ Jon

Edit1: допустим, у вас есть функция foo, которая что-то делает с итераторами (пример очень наивный!)

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

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

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

sort(IEnumerable<T> coll) 

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

sort(IEnumerator<T> iter)

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

Ответы [ 5 ]

6 голосов
/ 14 июня 2010

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

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

6 голосов
/ 14 июня 2010

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

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

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

и т.д.

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

РЕДАКТИРОВАТЬ: Чтобы ответить на ваш вопрос, отредактируйте: в C # (по крайней мере, с LINQ) вы не измените существующую коллекцию. Вы бы создали новую последовательность из старой. Это делается лениво; он не создает новую копию или что-то в этом роде. Для LINQ to Objects это выполняется с использованием методов расширения на IEnumerable<T>, поэтому любая последовательность получает те же способности.

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

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

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

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

3 голосов
/ 14 июня 2010

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

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

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 # one труднее использовать.

2 голосов
/ 14 июня 2010

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

Очень отличается от .NET, их ядро ​​- интерфейсы ICollection, IDictionary и IList. .NET Framework содержит сотни классов коллекций, настроенных для выполнения конкретной работы.

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

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

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

0 голосов
/ 30 декабря 2011

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

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

for(int i = 0; i< 1000; ++i)
{
    ... // do something

    if (i == 100)
       i = 200; // skip from 100 to 200
}

Дело в том, что переход к следующему элементу может быть оптимизирован для этой конкретной последовательности, но пропуск элементов (и произвольный произвольный доступ) обходится дороже. @Jon, поэтому использование IList для этой цели неэффективно. Урожай напрямую не разрешает этот стиль.

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

Подход, который, вероятно, приведет к наименьшим издержкам, - это простой тип итератора - не основанный на IEnumerable или yield -, который позволяет мне имитировать приведенный выше код.

Мысли

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...