Полное руководство по языку программирования С# 14 и платформе .NET 10 / Делегаты, события и лямбды

Замыкания

Начальный ⏱ 3 мин чтения 👁 2 🕓 обновлено 11.08.2026

Замыкание (closure) представляет объект функции, который запоминает свое лексическое окружение даже в том случае, когда она выполняется вне своей области видимости.

Технически замыкание включает три компонента:

  • внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные и параметры - лексическое окружение

  • переменные и параметры (лексическое окружение), которые определены во внешней функции

  • вложенная функция, которая использует переменные и параметры внешней функции

В языке C# реализовать замыкания можно разными способами - с помощью локальных функций и лямбда-выражений.

Рассмотрим создание замыканий через локальные функции:


var fn = Outer();   // fn = Inner, так как метод Outer возвращает функцию Inner
// вызываем внутреннюю функцию Inner
fn();   // 6
fn();   // 7
fn();   // 8

Action Outer()	// метод или внешняя функция
{
    int x = 5;	// лексическое окружение - локальная переменная
    void Inner()	// локальная функция
    {
        x++;		// операции с лексическим окружением
        Console.WriteLine(x);
    }
    return Inner;	// возвращаем локальную функцию
}

Здесь метод Outer в качестве возвращаемого типа имеет тип Action, то есть метод возвратить функцию, которая не принимает параметров и имеет тип void.

Action Outer()

Внутри метода Outer определена переменная x - это и есть лексическое окружение для внутренней функции:

int x = 5;

Также внутри метода Outer определена внутренняя функция - локальная функция Inner, которая обращается к своему лексическому окружению - переменной x - увеличивает ее значение на единицу и выводит на консоль:


void Inner()
{
	x++;
	Console.WriteLine(x);
}

Эта локальная функция возвращается методом Outer:

return Inner;

В программе вызываем метод Outer и получаем в переменную fn локальную функцию Inner:

var fn = Outer();

Переменная fn и представляет собой замыкание, то есть объединяет две вещи: функцию и окружение, в котором функция была создана. И несмотря на то, что мы получили локальную функцию и можем ее вызывать вне ее метода, в котором она определена, тем не менее она запомнила свое лексическое окружение и может к нему обращаться и изменять, что мы увидим по консольному выводу:


fn();   // 6
fn();   // 7
fn();   // 8

Реализация с помощью лямбда-выражений

С помощью лямбд можно сократить определение замыкания:


var outerFn = () =>
{
    int x = 10;
    var innerFn = () => Console.WriteLine(++x);
    return innerFn;
};

var fn = outerFn();   // fn = innerFn, так как outerFn возвращает innerFn
// вызываем innerFn
fn();   // 11
fn();   // 12
fn();   // 13

Применение параметров

Кроме внешних переменных к лексическому окружению также относятся параметры окружающего метода. Рассмотрим использование параметров:


var fn = Multiply(5);  

Console.WriteLine(fn(5));   // 25
Console.WriteLine(fn(6));   // 30
Console.WriteLine(fn(7));   // 35

Operation Multiply(int n)
{
    int Inner(int m)
    {
        return n * m;
    }
    return Inner;
}
delegate int Operation(int n);

Здесь внешняя функция - метод Multiply возвращает функцию, которая принимает число int и возвращает число int. Для этого определен делегат Operation, который будет представлять возвращаемый тип:

delegate int Operation(int n);

Хотя также можно было бы использовать встроенный делегат Func<int, int>.

Вызов метода Multiply() возвращает локальную функцию, которая соответствует сигнатуре делегата Operation:


int Inner(int m)
{
	return n * m;
}

Эта функция запоминает окружение, в котором она была создана, в частности, значение параметра n. Кроме того, сама принимает параметр и возвращает произведение параметров n и m.

В итоге при вызове метода Multiply определяется переменная fn, которая получает локальную функцию Inner и ее лексическое окружение - значение параметра n:

var fn = Multiply(5);

В данном случае параметр n равен 5.

При вызове локальной функции, например, в случае:

Console.WriteLine(fn(6));   // 30

Число 6 передается для параметра m локальной функции, которая возвращает произведение n и m, то есть 5 * 6 = 30.

Также можно было бы сократить весь этот код с помощью лямбд:


var multiply = (int n) => (int m) => n * m; 

var fn = multiply(5);  

Console.WriteLine(fn(5));   // 25
Console.WriteLine(fn(6));   // 30
Console.WriteLine(fn(7));   // 35

Проверь себя

📋 Тест по теме «Замыкания» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

var fn = _____();   // fn = Inner, так как метод Outer возвращает функцию Inner
// вызываем внутреннюю функцию Inner
fn();   // 6
fn();   // 7
fn();   // 8

Action Outer()	// метод или внешняя функция
{
    int x = 5;	// лексическое окружение - локальная переменная
    void Inner()	// локальная функция
    {
        x++;		// операции с лексическим окружением
        Console.WriteLine(x);
    }
    return Inner;	// возвращаем локальную функцию
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

Action _____()
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

void Inner()
{
	x++;
	Console._____(x);
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

return _____;
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

var fn = _____();
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

var fn = _____();   // fn = Inner, так как метод Outer возвращает функцию Inner
// вызываем внутреннюю функцию Inner
fn();   // 6
fn();   // 7
fn();   // 8

Action Outer()	// метод или внешняя функция
{
    int x = _____;	// лексическое окружение - локальная переменная
    void Inner()	// локальная функция
    {
        x++;		// операции с лексическим окружением
        Console.WriteLine(x);
    }
    return Inner;	// возвращаем локальную функцию
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

_____ _____()
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

Строки кода из примера этого урока перемешаны. Впишите их в правильном порядке, каждую строку — с новой строки.

void Inner()
	x++;
}
{
	Console.WriteLine(x);
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «Замыкания»
+30 XP

Опишите своими словами, что делает приведённый пример кода из урока «Замыкания», и предложите одно осмысленное изменение (например, другое входное значение или дополнительное условие). Приведите итоговый код.

var fn = Outer();   // fn = Inner, так как метод Outer возвращает функцию Inner
// вызываем внутреннюю функцию Inner
fn();   // 6
fn();   // 7
fn();   // 8

Action Outer()	// метод или внешняя функция
{
    int x = 5;	// лексическое окружение - локальная переменная
    void Inner()	// локальная функция
    {
        x++;		// операции с лексическим окружением
        Console.WriteLine(x);
    }
    return Inner;	// возвращаем локальную функцию
}
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «Замыкания»
+30 XP

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

Action Outer()
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «Замыкания»
+50 XP

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

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «Замыкания»
+25 XP

Напишите собственную небольшую программу, применяющую материал темы «Замыкания». Используйте не менее одной конструкции, показанной в примерах урока. Прикрепите код решения и краткое описание того, что он делает.

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.