Как мы увидели, основной для большинства коллекций является реализация интерфейсов IEnumerable и IEnumerator. Благодаря такой реализации мы можем перебирать объекты в цикле foreach:
1
2
3
4 |
foreach(var item in перечислимый_объект)
{
} |
Перебираемая коллекция должна реализовать интерфейс IEnumerable.
Интерфейс IEnumerable имеет метод, возвращающий ссылку на другой интерфейс - перечислитель:
1
2
3
4 |
public interface IEnumerable
{
IEnumerator GetEnumerator();
} |
А интерфейс IEnumerator определяет функционал для перебора внутренних объектов в контейнере:
1
2
3
4
5
6 |
public interface IEnumerator
{
bool MoveNext(); // перемещение на одну позицию вперед в контейнере элементов
object Current {get;} // текущий элемент в контейнере
void Reset(); // перемещение в начало контейнера
} |
Метод MoveNext() перемещает указатель на текущий элемент на следующую позицию в последовательности. Если последовательность еще не закончилась, то возвращает true. Если же последовательность закончилась, то возвращается false.
Свойство Current возвращает объект в последовательности, на который указывает указатель.
Метод Reset() сбрасывает указатель позиции в начальное положение.
Каким именно образом будет осуществляться перемещение указателя и получение элементов зависит от реализации интерфейса. В различным реализациях логика может быть построена различным образом.
Например, без использования цикла foreach перебирем коллекцию с помощью интерфейса IEnumerator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
using System;
using System.Collections;
namespace HelloApp
{
class Program
{
static void Main(string[] args)
{
int[] numbers = { 0, 2, 4, 6, 8, 10 };
IEnumerator ie = numbers.GetEnumerator(); // получаем IEnumerator
while (ie.MoveNext()) // пока не будет возвращено false
{
int item = (int)ie.Current; // берем элемент на текущей позиции
Console.WriteLine(item);
}
ie.Reset(); // сбрасываем указатель в начало массива
Console.Read();
}
}
} |
Реализация IEnumerable и IEnumerator
Рассмотрим простешую реализацию IEnumerable на примере:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 |
using System;
using System.Collections;
namespace HelloApp
{
class Week : IEnumerable
{
string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday" };
public IEnumerator GetEnumerator()
{
return days.GetEnumerator();
}
}
class Program
{
static void Main(string[] args)
{
Week week = new Week();
foreach(var day in week)
{
Console.WriteLine(day);
}
Console.Read();
}
}
} |
В данном случае класс Week, который представляет неделю и хранит все дни недели, реализует интерфейс IEnumerable. Однако в данном случае мы поступили очень просто - вместо реализации IEnumerator мы просто возвращаем в методе GetEnumerator объект IEnumerator для массива.
1
2
3
4 |
public IEnumerator GetEnumerator()
{
return days.GetEnumerator();
} |
Благодаря этому мы можем перебрать все дни недели в цикле foreach.
В то же время стоит отметить, что для перебора коллекции через foreach в принципе необязательно реализовать интерфейс IEnumerable. Достаточно в классе определить публичный метод GetEnumerator, который бы возвращал объект IEnumerator. Например:
1
2
3
4
5
6
7
8
9
10 |
class Week
{
string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday" };
public IEnumerator GetEnumerator()
{
return days.GetEnumerator();
}
} |
Однако это было довольно просто - мы просто используем уже готовый перчислитель массива. Однако, возможно, потребуется задать свою собственную логику перебора объектов. Для этого реализуем интерфейс IEnumerator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 |
using System;
using System.Collections;
namespace HelloApp
{
class WeekEnumerator : IEnumerator
{
string[] days;
int position = -1;
public WeekEnumerator(string[] days)
{
this.days = days;
}
public object Current
{
get
{
if (position == -1 || position >= days.Length)
throw new InvalidOperationException();
return days[position];
}
}
public bool MoveNext()
{
if(position < days.Length - 1)
{
position++;
return true;
}
else
return false;
}
public void Reset()
{
position = -1;
}
}
class Week
{
string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday" };
public IEnumerator GetEnumerator()
{
return new WeekEnumerator(days);
}
}
class Program
{
static void Main(string[] args)
{
Week week = new Week();
foreach(var day in week)
{
Console.WriteLine(day);
}
Console.Read();
}
}
} |
Здесь теперь класс Week использует не встроенный перечислитель, а WeekEnumerator, который реализует IEnumerator.
Ключевой момент при реализации перечислителя - перемещения указателя на элемент. В классе WeekEnumerator для хранения текущей позиции определена переменная position. Следует учитывать, что в самом начале (в исходном состоянии) указатель должен указывать на позицию условно перед первым элементом. Когда будет производиться цикл foreach, то данный цикл вначале вызывает метод MoveNext и фактически перемещает указатель на одну позицию в перед и только затем обращается к свойству Current для получения элемента в текущей позиции.
В примерах выше использовались необобщенные версии интерфейсов, однако мы также можем использовать их обобщенные двойники:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 |
using System;
using System.Collections;
using System.Collections.Generic;
namespace HelloApp
{
class Week
{
string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday" };
public IEnumerator<string> GetEnumerator()
{
return new WeekEnumerator(days);
}
}
class WeekEnumerator : IEnumerator<string>
{
string[] days;
int position = -1;
public WeekEnumerator(string[] days)
{
this.days = days;
}
public string Current
{
get
{
if (position == -1 || position >= days.Length)
throw new InvalidOperationException();
return days[position];
}
}
object IEnumerator.Current => throw new NotImplementedException();
public bool MoveNext()
{
if(position < days.Length - 1)
{
position++;
return true;
}
else
return false;
}
public void Reset()
{
position = -1;
}
public void Dispose() { }
}
class Program
{
static void Main(string[] args)
{
Week week = new Week();
foreach(var day in week)
{
Console.WriteLine(day);
}
Console.Read();
}
}
} |