История вопроса:
Оператор For Each...Next в Visual Basic позволяет перебрать элементы коллекций или массивов. Начиная с VB6 поддерживался перебор по стандартным коллекциям, а в VB.NET — по всем типам, реализующим определённый интерфейс. Но когда требуется создавать пользовательские коллекции для собственного перебора, важно правильно реализовать нужную инфраструктуру.
Проблема
Недостаточно реализовать хранение элементов — операторы For Each требуют поддержки специализированного интерфейса IEnumerable. Часто сборки кастомных коллекций ограничивались методом Add/Get, объект превращался в "чёрный ящик" для итерации. Это препятствовало интеграции с языковой структурой перебора и приводило к ошибкам.
Решение
Чтобы For Each корректно работал с вашей коллекцией, необходимо реализовать интерфейс IEnumerable, а для поддержки работы с типизированными элементами — IEnumerable(Of T). Нужно создать и собственный перечислитель (Enumerator), реализующий интерфейс IEnumerator (или IEnumerator(Of T) в generic-версиях).
Пример:
Public Class IntCollection Implements IEnumerable(Of Integer) Private ReadOnly items As New List(Of Integer)() Public Sub Add(value As Integer) items.Add(value) End Sub Public Function GetEnumerator() As IEnumerator(Of Integer) _ Implements IEnumerable(Of Integer).GetEnumerator Return items.GetEnumerator() End Function Private Function IEnumerable_GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator Return GetEnumerator() End Function End Class ' Использование: Dim col As New IntCollection() col.Add(10) col.Add(20) col.Add(30) For Each n As Integer In col Console.WriteLine(n) Next
Ключевые особенности:
Можно ли реализовать For Each без интерфейса IEnumerable, просто написав метод GetEnumerator?
Нет, Visual Basic требует официальной поддержки интерфейса IEnumerable/IEnumerable(Of T), чтобы компилятор распознал коллекцию как совместимую с For Each.
Должен ли перечислитель быть отдельным классом?
Нет, если база данных элементов поддерживает стандартный перечислитель (например, если вы агрегируете List(Of T)), можно возвращать его GetEnumerator. Только при сложных сценариях может понадобиться собственный класс-реализация IEnumerator.
Возможно ли модифицировать коллекцию внутри цикла For Each?
Нет, изменение коллекции во время итерации приведёт к InvalidOperationException. Для корректной обработки нужны специальные стратегии (например, копирование списка до перебора или использование индексации).
В компании был разработан собственный класс MyCollection с добавлением и удалением через массив, но без реализации интерфейса IEnumerable. For Each не работал, приходилось использовать обычные циклы и публичное поле с элементами.
Плюсы:
Минусы:
Класс переоборудовали, реализовав IEnumerable(Of T). После этого коллекция легко и стандартно использовалась в For Each, а также стала совместима с LINQ.
Плюсы:
Минусы: