クロージャーとキャプチャされた変数
ローカル関数が状態をキャプチャする仕組みを確認します。
「クロージャーとキャプチャされた変数」は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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ローカル関数の宣言
- クロージャーとキャプチャされた変数
- 静的ローカル関数
- ローカル関数とラムダの比較