← Назад к вопросам

Что такое замыкание (closure) в C#? Приведите пример.?

2.2 Middle🔥 201 комментариев
#C# и ООП

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое замыкание (closure) в C#

Замыкание в C# — это функция или лямбда-выражение, которое сохраняет доступ к переменным из внешней области видимости, даже после того, как эта область завершила выполнение. Это возможно благодаря тому, что компилятор C# создает неявный класс, который захватывает (инкапсулирует) эти переменные, продлевая их жизнь.

Ключевая идея: замыкание «замыкает» переменные вместе с функцией, позволяя ей использовать их в будущем, когда исходный контекст уже не существует.

Механизм работы

При создании замыкания компилятор C#:

  1. Определяет все внешние переменные, используемые внутри лямбды или анонимного метода.
  2. Генерирует новый приватный класс.
  3. Помещает захваченные переменные как поля этого класса.
  4. Реализует метод, соответствующий вашему лямбда-выражению, внутри этого класса.

Это позволяет переменным жить вместе с объектом класса, даже если их первоначальная область видимости (например, метод) уже завершилась.

Пример замыкания в C#

Рассмотрим классический пример — создание функций с помощью замыкания внутри цикла.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        // Создаем 5 функций, которые должны выводить индекс
        for (int i = 0; i < 5; i++)
        {
            // Лямбда захватывает переменную i
            actions.Add(() => Console.WriteLine(i));
        }

        // Вызываем все функции после завершения цикла
        foreach (Action action in actions)
        {
            action();
        }
    }
}

Результат выполнения (неожиданный для многих):

5
5
5
5
5

Почему так происходит?

Переменная i захватывается по ссылке, не по значению. Все лямбды ссылаются на одну и ту же переменную i. После завершения цикла её значение равно 5. Когда мы вызываем функции, они выводят текущее значение этой общей переменной.

Как правильно захватывать значение в цикле

Чтобы каждая функция сохранила своё уникальное значение, нужно создать локальную переменную внутри цикла, которую лямбда захватит.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        for (int i = 0; i < 5; i++)
        {
            // Локальная переменная для захвата
            int capturedValue = i;
            actions.Add(() => Console.WriteLine(capturedValue));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Результат:

0
1
2
3
4

Теперь каждая лямбда захватывает свою собственную копию capturedValue, потому что она создаётся на каждой итерации цикла.

Практическое применение замыканий в Unity

В Unity замыкания часто используются для:

  • Создания обработчиков событий с дополнительным контекстом.
  • Отложенного выполнения (например, в Invoke или корутинах).
  • Сохранения состояния в асинхронных операциях.
using UnityEngine;
using System.Collections;

public class ClosureExample : MonoBehaviour
{
    private int enemyCount = 0;

    void Start()
    {
        // Замыкание захватывает enemyCount
        StartCoroutine(SpawnEnemyWithDelay(3f));
    }

    IEnumerator SpawnEnemyWithDelay(float delay)
    {
        yield return new WaitForSeconds(delay);
        
        // Лямбда использует захваченный enemyCount даже после завершения Start()
        Debug.Log($"Enemy spawned! Total enemies: {++enemyCount}");
    }
}

Важные особенности замыканий в C#

  • Сборка мусора: захваченные переменные живут до тех пор, пока жива функция, которая их использует.
  • Потенциальные утечки памяти: если замыкание хранится долго (например, как статическое событие), захваченные объекты тоже не будут собраны.
  • Изменяемость: захваченные переменные можно изменять внутри замыкания, что влияет на их состояние для всех других функций, захвативших эту переменную.

Замыкания — мощный инструмент, но требуют понимания их механизма, чтобы избежать неожиданного поведения и проблем с памятью.

Что такое замыкание (closure) в C#? Приведите пример.? | PrepBro