Реализация перечислителя: использовать структуру или класс?
Я заметил, что 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
,