Могие почему-то боятся делать перечислители (Enumerator) для своих коллекций, выставляя наружу внутренние массивы List или Dictionary. Чуть более смелые разработчики, желая сохранить инкапсуляцию, выставляют из сущностей IEnumerable или даже более правильный IReadOnlyCollection, делая свои коллекции приватными, но доступными через свойство. А вот если нам нужно что-то сделать перед передачей элемента коллекции из сущности, то добро пожаловать в LINQ: коллеги просто возвращают IEnumerable где начинают городить в возвращаемом свойстве что-то вроде _collection.Select(id => new Actor(conext, id)).
Давайте не будем так делать. Перечислители писать легко, благодаря duck-typing'у, который только и требует от нас написать одно свойство (Current) и два метода (MoveNext и GetEnumerator). Для примера, вот так будет выглдяеть enumerator для ref struct (обратите внимание, что я использую современный синтаксис C#).
public ref struct Enumerator(int[] collection, MyContext context) {
public readonly Result Current => new(context, collection[_index]);
private int _index = -1;
public bool MoveNext() => ++_index < collection.Length;
}
В нашей собственной коллекции мы можем также, как и выше, воспользоваться duck-typing'ом, банально реализовав метод GetEnumerator:
pubic class MyCollection {
private int[] _collection;
private MyContext _context;
...
public Enumerator GetEnumerator() => new(_collection, _context);
...
}
Таким образом мы решаем сразу несколько проблем:
- Мы не выставляем наружу внутреннюю коллекцию.
- Мы возвращаем нормальный enumerator в виде struct, который ничего не аллоцирует.
- Мы не производим замыкание переменных (clousure) при перечислении (см. свойство Current, где можно нагородить любую логику).
Код примера в комментариях в телеграмм, если я объяснил слишком туманно.