Cамоучитель по VB.NET

         

For Each и интерфейс lEnumerable


Поддержка For-Each в классах VB6 была недостаточно интуитивной, а ее синтаксис воспринимался как нечто совершенно инородное (мы упоминали об этом в главе 1). В VB .NET существуют два способа организации поддержки For-Each в классах коллекций. Первый метод уже был продемонстрирован выше: новый класс определяется производным от класса с поддержкой For-Each и автоматически наследует его функциональность. В частности, этот способ применялся для класса Empl oyees, производного от класса System. Collections. CollectionBase.

Второй способ, основанный на самостоятельной реализации интерфейса IEnumerable, обеспечивает максимальную гибкость. Определение интерфейса выглядит следующим образом:

Public Interface lEnumerable

Function GetEnumerator() As Enumerator

End Interface

При реализации lEnumerable класс реализует метод GetEnumerator, который возвращает объект IEnumerator, обеспечивающий возможность перебора в классе. Метод перехода к следующему элементу коллекции определяется именно в интерфейсе IEnumerator, который определяется следующим образом:

Public Interface lEnumerator

Readonly Property Current As Object

Function MoveNext() As Boolean

Sub Reset ()

End Interface

В цикле For-Each перебор ведется только в одном направлении, а элементы доступны только для чтения. Этот принцип абстрагирован в интерфейсе lEnumerator — в интерфейсе присутствует метод для перехода к следующему элементу, но нет методов для изменения данных. Кроме того, в интерфейс IEnumerator должен входить обязательный метод для перехода в начало коллекции. Обычно этот интерфейс реализуется способом включения (containment): в коллекцию внедряется специальный класс, которому перепоручается выполнение трех интерфейсных методов (один из lEnumerable и два из IEnumerator).

Ниже приведен пример коллекции Employees, построенной «на пустом месте». Конечно, класс получается более сложным, чем при простом наследовании от System. Collections. CollectionBase, но зато он обладает гораздо большими возможностями. Например, вместо последовательного возвращения объектов Employee можно использовать сортировку по произвольному критерию:

1 Public Class Employees

2 Implements IEnumerable.IEnumerator

3 Private m_Employees() As Employee

4 Private m_index As Integer = -1

5 Private m_Count As Integer = 0

6 Public Function GetEnumerator() As lEnumerator _

7 Implements lEnumerable.GetEnumerator

8 Return Me

9 End Function

10 Public Readonly Property Current() As Object _

11 Implements IEnumerator.Current

12 Get

13 Return m_Employees(m_Index)

14 End Get

15 End Property

16 Public Function MoveNext() As Boolean _

17 Implements lEnumerator.MoveNext

18 If m_Index < m_Count Then

19 m_Index += 1

20 Return True

21 Else

22 Return False

23 End If

24 End Function

25 Public Sub Reset() Implements IEnumerator.Reset

26 m_Index = 0

27 End Sub

28 Public Sub New(ByVal theEmployees() As Employee)

29 If theEmployees Is Nothing Then

30 MsgBox("No items in the collection")

31 ' Инициировать исключение - см. главу 7

32 ' Throw New ApplicationException()

33 Else

34 m_Count = theEmployees.Length - 1

35 m_Employees = theEmployees

36 End If

37 End Sub

38 End Class

Строка 2 сообщает о том, что класс реализует два основных интерфейса, используемых при работе с коллекциями. Для этого необходимо реализовать функцию, которая возвращает объект lEnumerator. Как видно из строк 6-9, мы просто возвращаем текущий объект Me. Впрочем, для этого класс должен содержать реализации членов IEnumerable; они определяются в строках 10-27.

В приведенной выше программе имеется одна тонкость, которая не имеет никакого отношения к интерфейсам, а скорее связана со спецификой класса. В строке 4 переменная mjndex инициализируется значением -1, что дает нам доступ к 0 элементу массива, в результате чего первый вызов MoveNext предоставляет доступ к элементу массива с индексом 0 (попробуйте инициализировать mjndex значением 0, и вы убедитесь, что при этом теряется первый элемент массива).

Ниже приведена небольшая тестовая программа. Предполагается, что Publiс-класс Employee входит в решение:

Sub Main()

Dim torn As New Emplpyee("Tom". 50000)

Dim sally As New Employee("Sally". 60000)

Dim joe As New Employee("Joe", 10000)

Dim theEmployees(l) As Employee

theEmployees(0) = torn

theEmployees(1) = sally

Dim myEmployees As New Employees(theEmployees)

Dim aEmployee As Employee

For Each aEmployee In myEmployees

Console.WriteLine(aEmployee.TheName)

Next

Console.ReadLine()

End Sub





Содержание раздела