1. はじめに
知らなかったかもしれませんが、面接でよく出る好きな質問の一つが「ラムダ式について何を知っていますか?」です。なぜ聞かれるのかというと、C#でコードを短く、より表現力豊かに書ける便利で「流行り」の機能だからです。匿名メソッドが手書きの手紙のようなものだとしたら、ラムダ式はメッセンジャーでメッセージを送るようなもの:速く、コンパクトで、時には絵文字付き(まあ、ほとんど)です。
ラムダ式はC#にバージョン3.0で導入されて以来、LINQ、イベントハンドラ、スレッド、コレクション、さらには.NET上のAIライブラリまであらゆるところに広まりました。
ラムダ式とは、コードのその場で名前なしに超短く明確にメソッドを宣言する方法です。基本的な構文は常にこうなります:
(パラメータ) => 式_または_コードブロック
この「矢印」 => は“ラムダ演算子”または“矢印”と呼ばれます。
ちょっとした例え
レシピを書くときのことを思い出してみてください:「リンゴを取って、それを切って、ボウルに入れる」。
C#ではこうなります:
(リンゴ) => 切る(リンゴ)
2. 匿名メソッドをラムダに変える
たとえば、デリゲートがあるとします:
delegate int SquareDelegate(int x);
匿名メソッドはこんなふうになります:
SquareDelegate sq = delegate(int x) {
return x * x;
};
同じことをラムダ式で書くと:
SquareDelegate sq = (int x) => { return x * x; };
でも C#は型を推論できます ので、さらに短くできます:
SquareDelegate sq = x => x * x;
驚くほどコンパクト!
「通常の」メソッドや匿名関数との比較
| 方法 | 宣言場所 | 使える場所 | デメリット |
|---|---|---|---|
| 名前付きメソッド | クラス内 | どこでも | 名前が必要、コードが増える |
| 匿名メソッド | コード内 | デリゲートと一緒にのみ | 構文がやや冗長 |
| ラムダ式 | コード内 | デリゲートが使えるところならどこでも | 時に型が不明瞭になることがある |
3. ラムダ式の構文:バリエーション
パラメータ
パラメータなし:
Action hello = () => Console.WriteLine("こんにちは、世界!");
パラメータが一つ(カッコは省略可):
Func<int, int> inc = x => x + 1;
複数のパラメータ(カッコが必要):
Func<int, int, int> sum = (a, b) => a + b;
ラムダの本体
単一式 — 波括弧と return は不要:
x => x * x
コードブロック — 波括弧が必要で、return を使う必要がある:
(x, y) =>
{
int z = x + y;
return z * z;
}
パラメータの型
ほとんどの場合、型を明示する必要はありません — コンパイラが推論します。けれど明示してもOK:
(x, y) => x + y // コンパイラが型を推論します。
(int x, int y) => x + y // 明示することもできます。
4. ニュアンスとより実践的な例
コレクションでの使用
学習用の小さなアプリ(たとえばユーザーのリスト)を思い出してください。配列があるとします:
int[] numbers = { 1, 2, 3, 4, 5 };
偶数だけを選びたい:
var evenNumbers = numbers.Where(n => n % 2 == 0);
ここで Where はLINQの拡張メソッドで、条件がフィルタのラムダです。
メソッドへの渡し方
たとえば、デリゲート型のメソッドがあるとします:
public delegate bool Filter(int number);
public static int[] FilterNumbers(int[] data, Filter predicate)
{
var result = new List<int>();
foreach (var n in data)
if (predicate(n))
result.Add(n);
return result.ToArray();
}
ラムダ式を渡すと:
int[] evens = FilterNumbers(numbers, n => n % 2 == 0);
イベントハンドラとしてのラムダ
button.Click += (sender, args) => Console.WriteLine("ボタンが押されました!");
5. ラムダと標準ジェネリックデリゲート
ラムダは Func<>, Action<>, Predicate<> がないと想像しにくいです。これらは特別なデリゲート型です:
- Action — 値を返さない。
- Func — 値を返す。
- Predicate — bool を返し、通常フィルタに使う。
Action の例:
Action<string> log = message => Console.WriteLine(message);
log("このメッセージはラムダ経由で出力されます!");
Func の例:
Func<int, int, int> multiply = (a, b) => a * b;
int product = multiply(3, 5); // 15
Predicate の例:
Predicate<int> isNegative = n => n < 0;
bool test = isNegative(-7); // true
6. 複数の式を持つラムダ:ブロックが必要なとき
ラムダが複数の操作を行う必要がある場合、波括弧と明示的な return を使います:
Func<int, int, string> describeSum = (a, b) =>
{
int sum = a + b;
return $"合計: {sum}";
};
return がないとコンパイラが怒ります(ただし理由を親切には説明してくれず、「全ての経路が値を返していない」とか言われます)。
7. void の返却:Action
ラムダが何も返さないときは Action を使います:
Action greet = () => Console.WriteLine("笑ってください — コードはコンパイルします!");
ラムダは任意の数の文を含められます:
Action<int> printSquare = x =>
{
int sq = x * x;
Console.WriteLine($"数 {x} の二乗は {sq} です");
};
8. 制約と典型的なミス
時には自由にラムダを書きたくなりますが、コンパイラが必ずしもそれを許してくれるわけではありません。
外側でキャプチャされた変数と同じ名前の変数を宣言することはできません。
型には注意:デリゲートのシグネチャとラムダが一致しないとエラーになります。
デリゲートが戻り値を要求するなら、戻り値を返す必要があります。
複雑なラムダでは波括弧を忘れないでください。単一式なら括弧も return も不要。複数の文なら波括弧と return が必要です。
GO TO FULL VERSION