Реализация счетчика: используйте struct или class?
Я заметил, что List<T>
определяет свой счетчик как struct
, а ArrayList
определяет его перечислитель как class
. Какая разница? Если я должен написать перечислитель для моего класса, какой из них предпочтительнее?
EDIT: Мои требования не могут быть выполнены с помощью yield
, поэтому я использую собственный счетчик. Тем не менее, я задаюсь вопросом, было бы лучше следовать строкам List<T>
и реализовать его как структуру.
Ответы
Ответ 1
Как и другие, я бы выбрал класс. Смещаемые структуры неприятны. (И, как предлагает Джаред, я бы использовал блок итератора. Ручное кодирование перечислителя неудобно, чтобы получить право.)
Смотрите этот поток для примера перечислителя списка, являющегося изменчивой структурой, вызывающей проблемы...
Ответ 2
Самый простой способ написать перечислитель в С# - с шаблоном return yield. Например.
public IEnumerator<int> Example() {
yield return 1;
yield return 2;
}
Этот шаблон будет генерировать весь код перечислителя под капотом. Это принимает решение из ваших рук.
Ответ 3
Запишите его с помощью yield return
.
Относительно того, почему вы могли бы выбрать между class
или struct
, если вы сделаете его struct
, тогда он будет вставлен в коробку, как только он будет возвращен в качестве интерфейса, поэтому сделать его struct
просто причиной дополнительное копирование. Не могу понять этого!
Ответ 4
В списке причин используется перечислитель структуры, чтобы предотвратить вывоз мусора в операциях foreach. Это довольно веская причина, особенно если вы программируете Compact Framework, потому что CF не имеет генераторного GC, а CF обычно используется на низкопроизводительном оборудовании, где он может быстро привести к проблемам с производительностью.
Кроме того, я не думаю, что изменчивые структуры являются источником проблем в некоторых опубликованных примерах, но программисты, которые не имеют хорошего понимания того, как работают типы значений.
Ответ 5
Перечислитель по своей сути является изменяющейся структурой, так как ему необходимо обновить внутреннее состояние, чтобы перейти к следующему значению в исходной коллекции.
По-моему, структуры должны быть неизменными, поэтому я бы использовал класс.
Ответ 6
Любая реализация IEnumerable<T>
должна возвращать класс. Может быть полезно по соображениям производительности использовать метод GetEnumerator
, который возвращает структуру, которая предоставляет методы, необходимые для перечисления, но не реализует IEnumerator<T>
; этот метод должен отличаться от IEnumerable<T>.GetEnumerator
, который затем должен быть реализован явно.
Использование этого подхода позволит повысить производительность при перечислении класса с использованием цикла foreach или "Для каждого" на С# или vb.net или в любом контексте, где код, который выполняет перечисление, будет знать, что перечислитель является структурой, но избегайте ловушек, которые в противном случае возникали бы при пересылке счетчика и передавались по значению.
Ответ 7
Чтобы расширить на @Earwicker: вам обычно лучше не писать тип перечислителя, а вместо этого использовать yield return
, чтобы компилятор написал его для вас. Это связано с тем, что есть ряд важных тонкостей, которые вы можете пропустить, если вы сделаете это сами.
См. вопрос SO Какое ключевое слово yield используется для С#?" для получения более подробной информации о том, как его использовать.
Также Раймонд Чен имеет серию сообщений в блогах ( " Реализация итераторов в С# и ее последствия": parts 1, 2, 3 и 4), которые показывают, как правильно реализовать итератор без yield return
, который показывает, насколько он сложный, и почему вы должны просто использовать yield return
.
Ответ 8
Там пара blog posts которые охватывают именно этот вопрос. В принципе, перечислительные структуры - действительно очень плохая идея...