Sobes.tech
Назад к вопросам
Middle+
6

Как реализовать собственный Where без yield? Что такое yield и как он работает под капотом?

Компании, где спрашивали
Core outstaff

Ответ от нейросети

sobes.tech AI

yield в C# используется в итераторах для ленивой генерации последовательностей. Когда метод с yield return вызывается, он возвращает объект IEnumerable или IEnumerator, который при итерации последовательно возвращает значения, сохраняя состояние между вызовами.

Под капотом компилятор преобразует такой метод в конечный автомат (state machine), который хранит текущее состояние итерации и локальные переменные.

Чтобы реализовать собственный Where без yield, нужно вручную создать класс-итератор, реализующий IEnumerable<T> и IEnumerator<T>. В методе MoveNext будет логика фильтрации.

Пример упрощённой реализации Where без yield:

public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
    return new WhereEnumerable<T>(source, predicate);
}

class WhereEnumerable<T> : IEnumerable<T>
{
    private IEnumerable<T> _source;
    private Func<T, bool> _predicate;

    public WhereEnumerable(IEnumerable<T> source, Func<T, bool> predicate)
    {
        _source = source;
        _predicate = predicate;
    }

    public IEnumerator<T> GetEnumerator() => new WhereEnumerator(_source.GetEnumerator(), _predicate);
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    class WhereEnumerator : IEnumerator<T>
    {
        private IEnumerator<T> _sourceEnumerator;
        private Func<T, bool> _predicate;

        public WhereEnumerator(IEnumerator<T> sourceEnumerator, Func<T, bool> predicate)
        {
            _sourceEnumerator = sourceEnumerator;
            _predicate = predicate;
        }

        public T Current => _sourceEnumerator.Current;
        object IEnumerator.Current => Current;

        public bool MoveNext()
        {
            while (_sourceEnumerator.MoveNext())
            {
                if (_predicate(_sourceEnumerator.Current))
                    return true;
            }
            return false;
        }

        public void Reset() => _sourceEnumerator.Reset();
        public void Dispose() => _sourceEnumerator.Dispose();
    }
}

Таким образом, yield упрощает написание таких итераторов, скрывая сложность конечного автомата и состояния.