Реализация счетчика: используйте 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 которые охватывают именно этот вопрос. В принципе, перечислительные структуры - действительно очень плохая идея...