ラムダ式とLINQについて、サンプルコードを示しながら簡単にまとめてみました。
ラムダ式
そもそも、ラムダ式とは?と思った人もいると思います。
ラムダ式を一言で表すと、「匿名関数を作成するための簡潔な方法」と言えると思います。
サンプルコード⬇️
// 通常の関数
int Square(int x) { return x * x; }
// 同等のラムダ式
Func<int, int> square = x => x * x;
Console.WriteLine(square(5)); // 出力: 25
僕は最初、このコードをマイクロソフトのC#リファレンスで見た時に、無知すぎて「Funcって何?」って思いました。
Func<int, int>
とは、C#の組み込みデリゲート型の一つだそうです。
これは、一つ以上の入力パラメータと、一つの戻り値を持つメソッドを表現するために使用されます。
ちなみに、
デリゲート型とは、メソッドを参照するための型、と説明されることが多いです。
定義として、デリゲート型は、特定の戻り値の型と引数リストを持つメソッドを参照できる型です。これは、メソッドを変数として扱ったり、他のメソッドに渡したりすることを可能にします。(by claude先生)
以下にサンプルコードも示します。
// デリゲート型の宣言
public delegate int MathOperation(int x, int y);
class Program
{
static int Add(int a, int b) { return a + b; }
static int Multiply(int a, int b) { return a * b; }
static void Main(string[] args)
{
// デリゲートのインスタンス化
MathOperation operation = Add;
Console.WriteLine(operation(5, 3)); // 出力: 8
// デリゲートの再代入
operation = Multiply;
Console.WriteLine(operation(5, 3)); // 出力: 15
// ラムダ式を使用したデリゲートの作成
MathOperation subtract = (a, b) => a - b;
Console.WriteLine(subtract(5, 3)); // 出力: 2
}
}
Addメソッドが変数operationとして扱われていますよね。
僕はこのコードを見て先の定義にあった、「メソッドを変数として扱う」というところに納得しました。
話をFuncに戻して、もう少し理解を深めるために、Func<int, int>
分解して解説します。
- 最初の
int
:これは入力パラメータの型を表します。この場合、メソッドは1つのint型の引数を受け取ります。 - 2番目の
int
:これは戻り値の型を表します。この場合、メソッドはint型の値を返します。
つまり、Func<int, int>
は「int型の引数を1つ受け取り、int型の値を返すメソッド」を表現しています。
以上のことを踏まえて、もう一度コードを見ていきましょう。
Func<int, int> square = x => x * x;
これは以下のように解釈できます:
- squareは、int型の引数を1つ受け取り、int型の値を返す関数です。
- x => x * xは、その関数の具体的な実装です。xを受け取り、x * xを返します。
LINQ(Language Integrated Query)
LINQを一言で表すと、データクエリを直接C#コードに統合するための機能、と言えると思います。
サンプルコード⬇️
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// クエリ構文
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
// メソッド構文(ラムダ式を使用)
var evenNumbersMethod = numbers.Where(x => x % 2 == 0);
// numbersリストの中から偶数を検索
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
コレクションのフィルタリング、ソート、グループ化や、データベースクエリの作成(Entity Frameworkなどと組み合わせて)によく使われる印象。(初学者目線)
また、LINQには遅延実行と即時実行という概念がある。
遅延実行
定義:
クエリが定義された時点では実行されず、結果が実際に必要になるまで実行が遅延されること。
特徴:
- クエリ定義時にはデータは処理されない
- 結果が要求されたとき(例:foreach文での列挙時)に初めて実行される
- 必要な分だけデータが処理される(効率的)
サンプルコード⬇️
var numbers = Enumerable.Range(1, 10);
var evenNumbers = numbers.Where(n => n % 2 == 0);
// この時点では実際のフィルタリングは行われていない
// 以下のようにforeachで列挙したり、ToList()などを呼び出したりしたときに実行される
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
即時実行
定義:
クエリが定義された時点で即座に実行され、結果がメモリに保持されること。
特徴:
- クエリ定義時にデータが処理される
- 結果はメモリに保持される
- 大量データの場合はメモリ使用量に注意が必要
サンプルコード⬇️
var numbers = Enumerable.Range(1, 10);
var evenNumbersList = numbers.Where(n => n % 2 == 0).ToList();
// ToList()を呼び出した時点で、フィルタリングが実行され、結果がリストに格納される
主な即時実行メソッドには、ToList(), ToArray(), Count(), Sum(), Average(), Max(), Min() などがある。
遅延実行と即時実行の使い分け
遅延実行の利点:
- 大量データを扱う場合に効率的
- 必要な分だけデータを処理できる
- 複数のクエリを組み合わせる場合に柔軟性がある
即時実行の利点:
- 結果を即座に得られる
- 同じデータに対して複数回操作を行う場合に効率的
使用に注意すべき際のサンプルコード⬇️
var query = numbers.Where(n => n % 2 == 0);
// 以下のループは毎回クエリを実行するため非効率
for (int i = 0; i < 10; i++)
{
Console.WriteLine(query.Count());
}
// 代わりに以下のように即時実行を使うと効率的
var count = query.Count();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(count);
}
LINQを効果的に使用するには、遅延実行と即時実行の特性を理解し、適切に使い分けることが重要です。状況に応じて最適な方法を選択することで、パフォーマンスと可読性の高いコードを書くことができます。