Многие используют
yield return, но не задумываются что происходит внутри. Компилятор перестраивает весь метод в конечный автомат.Что делает компилятор
Вот простой метод:
public IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
Компилятор создаёт отдельный класс с полем
_state. Каждый yield return становится отдельным состоянием в switch:public bool MoveNext()
{
switch (_state)
{
case 0: _state = 1; _current = 1; return true;
case 1: _state = 2; _current = 2; return true;
case 2: _state = 3; _current = 3; return true;
default: return false;
}
}
foreach под капотом вызывает MoveNext() на каждой итерации. Локальные переменные метода становятся полями этого класса — так состояние и сохраняется между вызовами.Главное свойство — ленивость
Код выполняется только при вызове
MoveNext(). Поэтому можно работать с бесконечными последовательностями:public IEnumerable<int> Infinite()
{
int i = 0;
while (true) yield return i++;
}
Infinite().Take(5); // {0, 1, 2, 3, 4} — не зависнет
Практически
При загрузке данных из БД
yield return позволяет начать обработку до того, как все данные загрузятся:public IEnumerable<Order> GetOrders(IDataReader reader)
{
while (reader.Read())
yield return new Order { Id = reader.GetInt32(0) };
}
Каждый объект создаётся только при переходе к следующей итерации, а не все сразу в памяти.
При каждом вызове метода создаётся новый объект сгенерированного класса в куче. При частых вызовах это влияет на аллокации.
async/await работает по той же схеме — это два применения одного паттерна трансформации кода.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор