Реализация перечислителя: использовать структуру или класс?

Я заметил, что List<T> определяет свой перечислитель как struct, в то время как ArrayList определяет свой перечислитель как class, Какая разница? Если я хочу написать перечислитель для моего класса, какой из них предпочтительнее?

РЕДАКТИРОВАТЬ: мои требования не могут быть выполнены с помощью yieldтак что я реализую свой собственный перечислитель. Тем не менее, мне интересно, будет ли лучше следовать линиям List<T> и реализовать его как структуру.

8 ответов

Решение

Как и другие, я бы выбрал класс. Изменчивые структуры противны. (И, как предполагает Джаред, я бы использовал блок итератора. Ручное кодирование перечислителя - дело сложное.)

Посмотрите этот поток для примера перечислителя списка, являющегося изменчивой структурой, вызывающей проблемы...

Самый простой способ написать перечислитель в C# - использовать шаблон yield return. Например.

public IEnumerator<int> Example() {
  yield return 1;
  yield return 2;
}

Этот шаблон будет генерировать весь код перечислителя под капотом. Это берет решение из ваших рук.

Список причин использует перечислитель структуры для предотвращения генерации мусора в операторах foreach. Это довольно веская причина, особенно если вы программируете для Compact Framework, потому что CF не имеет генеративного GC, а CF обычно используется на низкопроизводительном оборудовании, где это может быстро привести к проблемам с производительностью.

Кроме того, я не думаю, что изменяемые структуры являются источником проблем в примерах, опубликованных некоторыми, но программисты, которые не имеют хорошего понимания того, как работают типы значений.

Перечислитель по своей сути является изменяющейся структурой, поскольку ему необходимо обновить внутреннее состояние, чтобы перейти к следующему значению в исходной коллекции.

На мой взгляд, структуры должны быть неизменными, поэтому я бы использовал класс.

Напишите это используя yield return,

Что касается того, почему вы могли бы иначе выбрать между class или же struct, если вы сделаете это struct затем он упаковывается, как только возвращается как интерфейс, поэтому struct просто вызывает дополнительное копирование. Не вижу смысла в этом!

Любая реализация IEnumerable<T> должен вернуть класс. Из соображений производительности может быть полезно иметь GetEnumerator метод, который возвращает структуру, которая предоставляет методы, необходимые для перечисления, но не реализует IEnumerator<T>; этот метод должен отличаться от IEnumerable<T>.GetEnumerator, который затем должен быть реализован в явном виде.

Использование этого подхода позволит повысить производительность, когда класс перечисляется с использованием цикла foreach или цикла "For Each" в C# или vb.net или в любом контексте, где код, выполняющий перечисление, будет знать, что перечислитель является структурой, но избегать подводные камни, которые в противном случае могли бы возникнуть, когда перечислитель упакован и передан по значению.

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

Чтобы расширить @Earwicker: обычно лучше не писать тип перечислителя, а вместо этого использовать yield return чтобы компилятор написал это для вас. Это потому, что есть ряд важных тонкостей, которые вы можете пропустить, если сделаете это сами.

См. Вопрос SO " Что такое ключевое слово yield, используемое в C#?" Для получения более подробной информации о том, как его использовать.

Также у Раймонда Чена есть серия постов в блоге (" Реализация итераторов в C# и ее последствия": части 1, 2, 3 и 4), которые показывают, как правильно реализовать итератор без yield return, который показывает, насколько это сложно, и почему вы должны просто использовать yield return,

Другие вопросы по тегам