0Pricing
C# Academy · レッスン

クロージャーとキャプチャされた変数

ローカル関数が状態をキャプチャする仕組みを確認します。

「クロージャーとキャプチャされた変数」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これは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());
    }
}

独立したクロージャ

ファクトリを呼び出すたびに、キャプチャされる変数の新しいセットが作成されます。この方法で作成した2つのカウンターは、互いに干渉しません。

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 ループでキャプチャする

典型的な落とし穴は、1つの 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));
    }
}

自分で試す

返されたクロージャによってプライベートな状態を保持するカウンターのファクトリを作成し、そこから独立した2つのカウンターを作ります。

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時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。

「クロージャーとキャプチャされた変数」で何を学びますか?

ローカル関数が状態をキャプチャする仕組みを確認します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「クロージャーとキャプチャされた変数」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ローカル関数の宣言
  2. クロージャーとキャプチャされた変数
  3. 静的ローカル関数
  4. ローカル関数とラムダの比較
← C# Academyに戻る