0Pricing
C# Academy · Урок

Замыкания и захваченные переменные

Узнайте, как локальные функции захватывают состояние.

«Замыкания и захваченные переменные» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Замыкания над локальным состоянием

Локальная функция (или лямбда-выражение) может читать и изменять переменные из содержащего её метода. Это называется захватом, а функция вместе с захваченными переменными образует замыкание.

Захват переменной

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

using System;

public class Program
{
    public static void Main()
    {
        int factor = 3;
        int Scale(int n) => n * factor; // captures factor

        Console.WriteLine(Scale(5));
        Console.WriteLine(Scale(10));
    }
}

Захваченные переменные остаются актуальными ссылками

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

using System;

public class Program
{
    public static void Main()
    {
        int offset = 10;
        int Add(int n) => n + offset;

        Console.WriteLine(Add(1)); // 11
        offset = 100;
        Console.WriteLine(Add(1)); // 101 - sees updated offset
    }
}

Изменение захваченных переменных

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

using System;

public class Program
{
    public static void Main()
    {
        int count = 0;
        void Tick() => count++;

        Tick(); Tick(); Tick();
        Console.WriteLine(count); // 3
    }
}

Возврат замыкания

Когда вы возвращаете лямбда-выражение, захватившее локальные переменные, эти переменные переживают завершение метода. Захваченное состояние живёт столько же, сколько и делегат.

using System;

public class Program
{
    static Func<int> MakeCounter()
    {
        int count = 0;
        return () => ++count; // captures count
    }

    public static void Main()
    {
        var next = MakeCounter();
        Console.WriteLine(next());
        Console.WriteLine(next());
        Console.WriteLine(next());
    }
}

Независимые замыкания

Каждый вызов фабрики создаёт новый набор захваченных переменных. Два созданных таким образом счётчика не влияют друг на друга.

using System;

public class Program
{
    static Func<int> MakeCounter()
    {
        int count = 0;
        return () => ++count;
    }

    public static void Main()
    {
        var a = MakeCounter();
        var b = MakeCounter();
        Console.WriteLine(a()); // 1
        Console.WriteLine(a()); // 2
        Console.WriteLine(b()); // 1 - separate state
    }
}

Исправление захвата в цикле foreach

В современном C# переменная цикла foreach создаётся заново на каждой итерации, поэтому замыкания захватывают ожидаемое значение. Каждое приведённое ниже лямбда-выражение запоминает собственный элемент.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var actions = new List<Action>();
        foreach (var i in new[] { 1, 2, 3 })
            actions.Add(() => Console.WriteLine(i));

        foreach (var act in actions) act(); // prints 1, 2, 3
    }
}

Захват в цикле for

Классическая ошибка: одна переменная цикла for используется во всех итерациях, поэтому все замыкания видят её последнее значение. Скопируйте её в локальную переменную, создаваемую для каждой итерации, чтобы захватить разные значения.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var actions = new List<Action>();
        for (int i = 0; i < 3; i++)
        {
            int copy = i; // fresh local each iteration
            actions.Add(() => Console.WriteLine(copy));
        }
        foreach (var act in actions) act(); // prints 0, 1, 2
    }
}

Замыкания захватывают по ссылке, а не по значению

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

using System;

public class Program
{
    public static void Main()
    {
        string message = "first";
        Action print = () => Console.WriteLine(message);

        print();          // first
        message = "second";
        print();          // second
    }
}

Практическое применение: настраиваемое поведение

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

using System;

public class Program
{
    static Func<double, double> MakeDiscount(double rate)
        => price => price * (1 - rate);

    public static void Main()
    {
        var tenOff = MakeDiscount(0.10);
        var halfOff = MakeDiscount(0.50);
        Console.WriteLine(tenOff(100));
        Console.WriteLine(halfOff(100));
    }
}

Попробуйте сами

Создайте фабрику счётчиков, возвращаемое замыкание которой сохраняет доступное ему частное состояние, а затем создайте с её помощью два независимых счётчика.

using System;

public class Program
{
    static Func<int> MakeStepper(int step)
    {
        int current = 0;
        return () => { current += step; return current; }; // captures current and step
    }

    public static void Main()
    {
        var byTwos = MakeStepper(2);
        var byTens = MakeStepper(10);
        Console.WriteLine(byTwos()); // 2
        Console.WriteLine(byTwos()); // 4
        Console.WriteLine(byTens()); // 10 - separate state
    }
}

Быстрая проверка

Разберитесь, как работает захват.

Повторение

Замыкания захватывают переменные из содержащего контекста по ссылке.

  • Функция видит текущее значение переменной, включая последующие изменения.
  • Она может изменять захваченные переменные, совместно используя состояние с методом.
  • Возвращённые замыкания сохраняют захваченные переменные.
  • В цикле for используйте локальную переменную для каждой итерации, чтобы захватывать разные значения.

Часто задаваемые вопросы

Урок «Замыкания и захваченные переменные» бесплатный?

Да — полный текст урока «Замыкания и захваченные переменные» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Замыкания и захваченные переменные»?

Узнайте, как локальные функции захватывают состояние. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Замыкания и захваченные переменные»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Объявление локальных функций
  2. Замыкания и захваченные переменные
  3. Статические локальные функции
  4. Локальные функции и лямбда-выражения
← Назад к C# Academy