C#: ラムダ式
1. ラムダ式の概要
ラムダ式とは、簡潔な匿名関数の構文であり、より少ないコードでインライン関数を定義することができます。これはC#の関数型プログラミングの中核となる機能であり、LINQクエリの基盤となっています。
2. ラムダ構文
ラムダ式の基本的な構文は次のとおりです:parameters => expression or statement block。=>は「~へ」と読み、左辺のパラメータを右辺の演算に割り当てます。
▶ サンプル
using System;
class Program
{
static void Main()
{
Func`<int, int>` square = x => x * x;
Console.WriteLine(square(5));
}
}
25
3. 1行および複数行のラムダ式
1行のラムダ式は、returnキーワードや中括弧を指定せずに、式の結果を直接返します。複数行のラムダ式では、ステートメントブロックを囲むために中括弧が必要であり、明示的なreturnステートメントも指定する必要があります。
▶ サンプル
using System;
class Program
{
static void Main()
{
Func`<int, int>` singleLine = x => x * x;
Func`<int, int>` multiLine = x =>
{
int result = x * x;
return result;
};
Console.WriteLine(singleLine(4));
Console.WriteLine(multiLine(4));
}
}
16
16
4. 型推論
通常、コンパイラは文脈からラムダ式のパラメータの型を推論できるため、明示的な宣言は必要ありません。文脈から型が判別できない場合は、手動で型を指定することができます。
▶ サンプル
using System;
class Program
{
static void Main()
{
Func`<int, int>` inferred = x => x + 1;
Func`<int, int>` explicitType = (int x) => x + 1;
Console.WriteLine(inferred(10));
Console.WriteLine(explicitType(10));
}
}
11
11
5. ラムダ式とデリゲート
ラムダ式はデリゲート型に代入できるため、デリゲートインスタンスを作成する上で最も簡潔な方法となります。従来の匿名メソッドや明示的なデリゲートのインスタンス化と比較して、ラムダ式の方が記述が簡潔です。
▶ サンプル
using System;
delegate int Transform(int x);
class Program
{
static void Main()
{
Transform doubleIt = x => x * 2;
Transform addTen = x => x + 10;
Console.WriteLine(doubleIt(3));
Console.WriteLine(addTen(3));
}
}
6
13
6. func および action を使用したラムダ式
Func<T, TResult>は戻り値を持つジェネリックデリゲートを表し、Action<T>は戻り値を持たないジェネリックデリゲートを表します。これらとラムダ式を組み合わせて使用することは非常に一般的です。
▶ サンプル
using System;
class Program
{
static void Main()
{
Func`<int, int, int>` add = (a, b) => a + b;
Action```<string>``` greet = msg => Console.WriteLine(msg);
Console.WriteLine(add(3, 7));
greet("Hello");
}
}
10
Hello
7. ラムダ式と述語
Predicate<T>は、boolを返すデリゲートであり、フィルタリングや条件チェックによく使用されます。ラムダ式を使えば、条件を非常に簡潔に定義できます。
▶ サンプル
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var numbers = new List```<int>``` { 1, 2, 3, 4, 5, 6 };
Predicate```<int>``` isEven = x => x % 2 == 0;
var evens = numbers.FindAll(isEven);
Console.WriteLine(string.Join(", ", evens));
}
}
2, 4, 6
8. クロージャ
ラムダ式は外側の変数をキャプチャすることができ、これを「クロージャ」と呼びます。キャプチャされた変数の存続期間はラムダ式とともに延長され、元のスコープが終了した後も、それらの変数は存続し続けます。
▶ サンプル
using System;
class Program
{
static void Main()
{
int factor = 3;
Func`<int, int>` multiply = x => x * factor;
Console.WriteLine(multiply(5));
factor = 10;
Console.WriteLine(multiply(5));
}
}
15
50
▶ サンプル
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var actions = new List`<Action>`();
for (int i = 0; i < 3; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
foreach (var action in actions)
{
action();
}
}
}
0
1
2
iを直接キャプチャすると、すべてのラムダ式が同じiを共有することになり、最終的な出力はすべて3になってしまいます。ローカルコピーを作成することで、この問題を回避できます。
9. LINQにおけるラムダ式 — 入門編
ラムダ式は、LINQ クエリの中心的な要素です。多くの LINQ メソッドは Func または Expression パラメータを受け付けますが、ラムダ式を使用することで、条件、プロジェクション、およびソートロジックをインラインで簡潔に記述することができます。
▶ サンプル
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var numbers = new List```<int>``` { 3, 7, 1, 9, 4, 6 };
var big = numbers.Where(x => x > 5);
var doubled = numbers.Select(x => x * 2);
var sorted = numbers.OrderBy(x => x);
Console.WriteLine(string.Join(", ", big));
Console.WriteLine(string.Join(", ", doubled));
Console.WriteLine(string.Join(", ", sorted));
}
}
7, 9, 6
6, 14, 2, 18, 8, 12
1, 3, 4, 6, 7, 9
10. 式ツリーの概要
Expression<TDelegate>にラムダ式が割り当てられると、コンパイラは実行可能コードではなく、データ構造(式木)を生成します。式木は解析や変更が可能であり、SQLクエリなどの他の形式に変換することもできます。これが、LINQ to SQLなどの技術の根底にある仕組みです。
▶ サンプル
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func`<int, int>`> expr = x => x * x + 1;
Console.WriteLine(expr.Body);
Console.WriteLine(expr.Parameters[0]);
var compiled = expr.Compile();
Console.WriteLine(compiled(4));
}
}
(x * x) + 1
x
17
ℹ️
Expression<Func``<int,int>``>およびFunc<int,int>は異なるタイプです。前者はデータ構造であり、後者は実行可能なデリゲートです。
❓ よくある質問
📖 まとめ
- ラムダ構文は
parameters => expressionで、匿名関数を簡潔に定義します - 1行のラムダ式では中括弧とreturn文を省略できますが、複数行のラムダ式ではその両方が必要です
- コンパイラはラムダ式のパラメータ型を推論できます。必要に応じて明示的な宣言を行うことも可能です。
- ラムダ式は、カスタムデリゲート、Func、Action、Predicate に直接代入できます
- クロージャーにより、ラムダ式は外側の変数をキャプチャすることができ、その存続期間を延長できる
- ループ変数をキャプチャする際は、意図しない共有を避けるためにコピーを作成してください
- ラムダ式は、LINQ メソッドにおける主要なパラメータ形式です
- 式ツリーはラムダ式をデータ構造として表現し、実行時の解析や変換を可能にする
📝 練習問題
- 2つの数値の最大値を計算する
Func<double, double, double>ラムダ関数を作成してください Action<string>ラムダ関数を使用して、タイムスタンプ付きのログメッセージを出力する- クロージャの例を作成してください:呼び出されるたびに1ずつ増える整数を返すカウンター関数を作成してください
- Lambda関数で
Whereを使用し、文字列のリストから5文字を超える文字列をフィルタリングする Func<int, bool>のラムダ式をFunc<int, bool>とExpression<Func``<int, bool>``>の両方に割り当て、それぞれのGetType()の結果を出力する