Day 66 のゴールと全体像
Day 66 では、C# の“ちょっと大人な機能”である delegate・ラムダ式 に入門していきます。 今日のキーワードは次の4つです。
- ラムダ式
- Func
- Action
- コールバックの基礎
どれも、
「メソッドそのものを“値”として扱う」
ための道具です。 最初は少し不思議な感じがするかもしれませんが、 一度イメージがつかめると、コードの表現力がぐっと広がっていきます。
ここでは、
- ラムダ式って何者?
- Func / Action はどう使う?
- コールバックってどういう場面で出てくる?
という流れを、例題コードを交えながら、 ステップバイステップでかみ砕いて説明していきます。
「メソッドを値として扱う」という発想
メソッドを変数に入れるってどういうこと?
ふつう、メソッドはこう書きますよね。
static int Add(int x, int y)
{
return x + y;
}
C#そして、呼び出すときはこうです。
int result = Add(3, 5);
C#ここまでは「メソッドは呼び出すもの」という感覚だと思います。
でも、C# では
「メソッドそのものを変数に入れて、あとから呼び出す」
ということができます。
この「メソッドを変数に入れる」ための仕組みが delegate であり、 それをもっと使いやすくしたものが Func / Action / ラムダ式 です。
ラムダ式とは何か
ラムダ式の見た目
ラムダ式は、
「その場でサッと書ける“匿名メソッド”」
のようなものです。
いちばんシンプルな形は、
x => x * 2
C#という書き方です。
これは、
- 引数:
x - 処理:
x * 2を返す
という「小さなメソッド」を、その場で書いているイメージです。
もう少し丁寧な形
(x, y) => x + y
C#これは、
- 引数:
xとy - 処理:
x + yを返す
というメソッドです。
さらに、処理が複数行になる場合は、
(x, y) =>
{
int sum = x + y;
Console.WriteLine($"合計は {sum} です。");
return sum;
}
C#のように、波括弧 {} を使って書くこともできます。
ラムダ式のざっくりしたイメージ
ラムダ式は、
「名前を付けずに、その場でメソッドを書くための記法」
だと思ってもらうと、かなりイメージしやすくなります。
Func と Action の基本
Func とは
Func は、
「引数があって、戻り値もあるメソッド」を表すための型
です。
書き方は少し独特ですが、 慣れてしまえば「パターン」として見えてきます。
例:2つの int を受け取って、int を返す Func
Func<int, int, int> add = (x, y) => x + y;
C#ここでの意味は、
Func<int, int, int>- 最後の
intが「戻り値の型」 - それより前の
int, intが「引数の型」
- 最後の
(x, y) => x + y- 「x と y を受け取って、x + y を返すラムダ式」
という形です。
呼び出すときは、
int result = add(3, 5);
Console.WriteLine($"結果: {result}"); // 8 が表示される
C#と、ふつうのメソッドと同じように add(…) と書きます。
Action とは
Action は、
「引数はあるけれど、戻り値がないメソッド」を表すための型
です。
例:string を受け取って、表示するだけの Action
Action<string> print = message =>
{
Console.WriteLine($"メッセージ: {message}");
};
C#ここでの意味は、
Action<string>- 引数の型が
string - 戻り値はなし(
voidのイメージ)
- 引数の型が
message => { ... }- 「message を受け取って、Console.WriteLine する」
という形です。
呼び出すときは、
print("こんにちは");
C#と書くだけです。
Func と Action の違いを整理
- Func
- 「戻り値あり」のメソッドを表す
- 最後の型が戻り値、それより前が引数
- Action
- 「戻り値なし」のメソッドを表す
- 型パラメータは引数だけ
この2つを覚えておくと、 「メソッドを変数に入れて扱う」場面で、とても役に立ちます。
ラムダ式 + Func / Action を組み合わせてみる
例:配列の要素を加工する処理を差し替える
同じ配列に対して、
- 2倍にする
- 3倍にする
- マイナスにする
など、いろいろな加工をしたくなることがあります。
そんなときに、 「加工の仕方」をラムダ式で渡せると、とても柔軟になります。
using System;
class Program
{
static void Main()
{
int[] numbers = { 1, 2, 3, 4, 5 };
// 2倍にする処理
Console.WriteLine("2倍にした結果:");
ProcessArray(numbers, x => x * 2);
// 3倍にする処理
Console.WriteLine("3倍にした結果:");
ProcessArray(numbers, x => x * 3);
// マイナスにする処理
Console.WriteLine("マイナスにした結果:");
ProcessArray(numbers, x => -x);
}
// 配列の各要素に「加工処理」を適用して表示するメソッド
static void ProcessArray(int[] array, Func<int, int> processor)
{
foreach (int n in array)
{
int result = processor(n); // 渡された処理を適用
Console.WriteLine(result);
}
}
}
C#ここでのポイントは、
ProcessArrayが「配列を処理する枠組み」を提供している- 実際に「どう加工するか」は、
Func<int, int> processorに任せている - 呼び出し側で、ラムダ式
x => x * 2などを渡している
というところです。
「枠組み」と「具体的な処理」を分けて書けるようになる
のが、ラムダ式 + Func の大きな魅力です。
コールバックの基礎
コールバックって何?
コールバックは、
「ある処理が終わったタイミングで、呼び出してほしいメソッド」
のことです。
たとえば、
- 「時間のかかる処理が終わったら、結果を表示したい」
- 「ボタンが押されたら、この処理を実行したい」
といった場面で、 「終わったときに呼んでね」「押されたときに呼んでね」と メソッドを“登録”しておくイメージです。
例:処理が終わったらメッセージを表示するコールバック
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("長い処理を開始します…");
// コールバックとして、処理完了時に呼んでほしいメソッドを渡す
await DoLongWorkAsync(OnWorkCompleted);
Console.WriteLine("Main の処理も終わりました。");
}
// 時間のかかる処理
static async Task DoLongWorkAsync(Action<string> callback)
{
Console.WriteLine("処理中…");
await Task.Delay(2000); // 2秒待つ(長い処理のイメージ)
Console.WriteLine("処理が終わりました。");
// 終わったタイミングでコールバックを呼ぶ
callback("処理が正常に完了しました。");
}
// コールバックとして呼ばれるメソッド
static void OnWorkCompleted(string message)
{
Console.WriteLine($"コールバック: {message}");
}
}
C#ここでの流れは、
MainからDoLongWorkAsync(OnWorkCompleted)を呼ぶ- 「処理が終わったら
OnWorkCompletedを呼んでね」と渡している
- 「処理が終わったら
DoLongWorkAsyncの中で、2秒待つ- 終わったタイミングで
callback("…")を呼ぶ- 実際には
OnWorkCompleted("…")が呼ばれる
- 実際には
OnWorkCompletedがメッセージを表示する
という形になっています。
ラムダ式をコールバックとして渡す
コールバックは、 わざわざ別メソッドを定義しなくても、 ラムダ式でその場で書いて渡すこともできます。
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("長い処理を開始します…");
await DoLongWorkAsync(message =>
{
Console.WriteLine($"ラムダ式コールバック: {message}");
});
Console.WriteLine("Main の処理も終わりました。");
}
static async Task DoLongWorkAsync(Action<string> callback)
{
Console.WriteLine("処理中…");
await Task.Delay(2000);
Console.WriteLine("処理が終わりました。");
callback("処理が正常に完了しました。");
}
}
C#このように、
- コールバックの枠組みは
Action<string>で用意しておき - 呼び出し側で「何をしたいか」をラムダ式で渡す
というスタイルは、 実践的なコードで非常によく使われます。
delegate から Func / Action への橋渡し
伝統的な delegate の書き方
C# には、もともと delegate というキーワードがあります。
// int を受け取って int を返すメソッドを表す delegate 型
delegate int IntOperation(int x);
C#これを使うと、
using System;
class Program
{
static void Main()
{
IntOperation op = Double;
int result = op(10);
Console.WriteLine($"結果: {result}");
}
static int Double(int x)
{
return x * 2;
}
}
C#という書き方ができます。
ただ、
- 型ごとに
delegateを定義する必要がある - 少し記述が重くなりがち
という理由から、 今は Func / Action + ラムダ式 を使うスタイルが主流です。
Func / Action は「汎用的な delegate」
Func<T1, T2, TResult>Action<T1, T2>
といった型は、
「よく使うパターンの delegate を、あらかじめ用意してくれているもの」
だと思ってもらうと、しっくり来ると思います。
Day 66 用ミニテンプレート
最後に、今日の内容をそのまま使える形のテンプレートとしてまとめておきます。
ラムダ式 + Func の基本形
// 2つの int を受け取って、int を返す処理
Func<int, int, int> add = (x, y) => x + y;
int result = add(3, 5);
Console.WriteLine($"結果: {result}");
C#ラムダ式 + Action の基本形
// string を受け取って、表示するだけの処理
Action<string> print = message =>
{
Console.WriteLine($"メッセージ: {message}");
};
print("こんにちは");
C#コールバックの基本形
static async Task Main()
{
await DoWorkAsync(message =>
{
Console.WriteLine($"コールバックで受け取ったメッセージ: {message}");
});
}
static async Task DoWorkAsync(Action<string> callback)
{
Console.WriteLine("処理中…");
await Task.Delay(1000);
Console.WriteLine("処理が終わりました。");
callback("処理完了!");
}
C#Day 66 のまとめ
Day 66 では、
- ラムダ式(その場で書ける“匿名メソッド”)
- Func(戻り値ありのメソッドを表す型)
- Action(戻り値なしのメソッドを表す型)
- コールバック(「終わったときに呼んでね」というメソッドの登録)
という、C# の“メソッドを値として扱う”ための道具たちを、 例題コードとともにじっくり見ていきました。
最初は少し抽象的に感じるかもしれませんが、
- 小さなラムダ式を書いてみる
- Func / Action を使って「処理の差し替え」を試してみる
- コールバックの枠組みを作って、ラムダ式で中身を渡してみる
といった練習を重ねていくうちに、 「メソッドを渡す」「メソッドを登録する」という感覚が、 自然と手になじんでいきます。
ぜひ、ラムダ式や Func / Action を、
「コードの柔軟さと表現力を一段引き上げてくれる、頼れる相棒」
として、今後のプログラミングに活かしていっていただければうれしいです。
