Когда не стоит использовать итератор?

116
19 ноября 2019, 06:50

Когда использование паттерна итератор уместно, а в каких случаях нежелательно?

Какие подводные камни скрывает паттерн?

Например, у меня есть класс для чтения строк из файла:

public class FormattedFileReader{
    private readonly string _path;
    // ...     
    public static IEnumerable<string> ReadFromFile(){
        foreach (var line in File.ReadLines(_path)){
            yield return GetFormattedValue(line);
        }
    }
    private string GetFormattedValue(string initial){
        // ...
    }
}
Answer 1

Что пишут про итераторы на MSDN

Скажу своё мнение насчёт использования итератора.

Когда бы не использовал я:

  • Если нужна производительность, а именно быстрый обход какой-то коллекции с индексатором, а значит не использовал бы foreach
  • Если итератор использует внутри метода IEnumerator<T> GetEnumerator() yield return и при этом нам лучше не держать открытым соединение, например, с БД длительное время, ведь мы точно не можем быть уверенными, что внутри вызывающего цикла foreach мы в приемлемое время обойдём всю коллекцию и закроем соединение или обойдёт её кто-то другой, не посвещённый в наши планы относительно быстрого обхода коллекции.

Примеры, где я считаю использование итератора очень даже уместным:

Обход части коллекции

/// <summary>
/// Класс-итератор для прохода по части исходной коллекции, реализующей <see cref="IReadOnlyList{T}"/>.
/// </summary>
/// <typeparam name="T"> Тип элементов коллекции. </typeparam>
public struct SubListIterator<T> : IEnumerable<T>
{
    private readonly IReadOnlyList<T> _original;
    private readonly int _start;
    public SubListIterator(IReadOnlyList<T> original, int start, int length)
    {
        if (original == null)
        {
            throw new ArgumentNullException(nameof(original));
        }
        if (start < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(start));
        }
        if (length < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(length));
        }
        if (start + length > original.Count)
        {
            throw new Exception(
                "The range exceeds the size of the original IReadOnlyList " +
                $"({nameof(start)} + {nameof(length)} = {(start + length).ToString()}, size of IReadOnlyList = {original.Count}).");
        }
        Length = length;
        _original = original;
        _start = start;
    }
    public T this[int index]
    {
        get
        {
            if (index < 0 || index >= Length)
            {
                throw new IndexOutOfRangeException();
            }
            return _original[_start + index];
        }
    }
    public int Length { get; }
    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < Length; i++)
        {
            yield return _original[_start + i];
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Метод-расширение для итератора

public static class IReadOnlyListExtensions
{
    public static IEnumerable<SubListIterator<T>> GetPart<T>(this IReadOnlyList<T> list, int partSize)
    {
        int newPartSize;
        for (int i = 0; i < list.Count; i += newPartSize)
        {
            newPartSize = partSize + i > list.Count ? list.Count - i : partSize;
            yield return new SubListIterator<T>(list, i, newPartSize);
        }
    }
}

Чтение из файла, если нам не принципиально закрывать это файл быстро

/// <summary>
/// Класс для чтения из файла данных по частям.
/// </summary>
public class TextFileIterator : IEnumerable<IReadOnlyList<string>>
{
    private readonly string _pathToFile;
    private readonly int _maxLines;
    private readonly CancellationToken _cancellationToken;
    private readonly Encoding _encoding;
    public TextFileIterator(string pathToFile, int maxLines, CancellationToken cancellationToken, Encoding encoding = null)
    {
        Debug.Assert(maxLines > 0);
        Debug.Assert(pathToFile != null);
        Debug.Assert(cancellationToken != null);
        _pathToFile = pathToFile;
        _maxLines = maxLines;
        _cancellationToken = cancellationToken;
        if (encoding == null)
        {
            encoding = Encoding.UTF8;
        }
        _encoding = encoding;
    }
    public IEnumerator<IReadOnlyList<string>> GetEnumerator()
    {
        using (var file = new StreamReader(_pathToFile, _encoding))
        {
            var lines = new List<string>(_maxLines);
            do
            {
                lines.Clear();
                string line = null;
                for (int i = 0; i < _maxLines; i++)
                {
                    if ((line = file.ReadLine()) == null)
                    {
                        break;
                    }
                    if (_cancellationToken.IsCancellationRequested)
                    {
                        yield break;
                    }
                    lines.Add(line);
                }
                if (lines.Count > 0)
                {
                    yield return lines.ToArray();
                }
                if (line == null)
                {
                    break;
                }
            } while (!_cancellationToken.IsCancellationRequested);
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
READ ALSO
Как правильно и компактно распарсить json?

Как правильно и компактно распарсить json?

Получаю от сайта ответ в формате json ( сайт ) как можно максимально компактно распарсить такой json?

125
Как лучше сделать google auth

Как лучше сделать google auth

В проекте API использую google auth так:

147
Как упаковать шары в сфере?

Как упаковать шары в сфере?

Есть алгоритм для построения шаров по поверхности сферы

136
RichTextBox как пользоваться

RichTextBox как пользоваться

Подскажите пожалуйста, как пользоваться RichTextBox? Как вставлять текст и менять его внешний вид(цвет или задний фон отдельных слов и тп

120