История вопроса
Итераторы стали важной частью языков программирования с появлением коллекций, которые надо перебирать. В Visual Basic с версии .NET появилась поддержка IEnumerable/IEnumerator, позволяющая реализовывать собственные перебираемые коллекции и использовать цикл For Each для удобного и лаконичного доступа к содержимому коллекций.
Проблема
Многие разработчики ограничиваются использованием только встроенных коллекций, либо не реализуют корректно интерфейс IEnumerable, из-за чего невозможен перебор объектов через For Each. Кроме того, сложности вызывает реализация и поддержка состояния итератора через Current и MoveNext.
Решение
Для поддержки итераций тип реализует интерфейс IEnumerable (или обобщённый IEnumerable(Of T)). Реализуйте функцию GetEnumerator, возвращающую объект, реализующий IEnumerator. В VB.NET можно применять также итераторные методы с ключевым словом Yield (начиная с VB 2015), что сильно упрощает реализацию перебора.
Пример кода:
Imports System.Collections Public Class SimpleCollection Implements IEnumerable Private arr() As Integer = {1, 2, 3} Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator For Each i In arr Yield i ' для VB 2015+ Next End Function End Class ' Использование: For Each n As Integer In New SimpleCollection() Console.WriteLine(n) Next
Ключевые особенности:
Обязательно ли реализовывать оба интерфейса: IEnumerable и IEnumerator, для поддержки For Each?
Нет, достаточно реализации IEnumerable и возвращения совместимого итератора. Если вы хотите реализовать перебор, вы можете использовать уже существующий IEnumerator у вложенной коллекции. Но для полной кастомизации необходима реализация обеих частей.
Пример кода:
Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator Return arr.GetEnumerator() ' используем встроенный итератор массива End Function
Можно ли реализовать IEnumerable только явно (Explicit Interface Implementation) в Visual Basic?
Да, можно реализовать интерфейс явно, особенно когда у вашей коллекции уже есть свой собственный GetEnumerator, и вы хотите скрыть реализацию интерфейса. Вызвать такой GetEnumerator напрямую не получится, но For Each будет работать.
Пример кода:
Public Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator ' ...тело метода End Function
Что произойдет, если GetEnumerator возвращает Nothing?
Будет выброшено исключение в момент первой попытки перебора коллекции, так как For Each ожидает корректного итератора.
Класс коллекции не реализовал IEnumerable, а отдельный метод Next был создан для перебора элементов. В итоге его нельзя использовать в универсальных методах или For Each, приходится писать дополнительные методы.
Плюсы:
Минусы:
Разработчик реализовал IEnumerable и GetEnumerator, коллекция теперь работает с For Each и интегрируется с LINQ.
Плюсы:
Минусы: