C# ラムダ式のパフォーマンス考慮点

ラムダ式は便利な機能ですが、使い方によってはパフォーマンスに影響を与えることがあります。特にホットパスでは、いくつかの点に注意が必要です。

クロージャによるアロケーション

外部変数をキャプチャするラムダ式は、内部的にクロージャクラスが生成されます。これはヒープへのメモリ割り当てを伴います。

void ProcessWithClosure(List<int> numbers)
{
    int threshold = 10;
    
    // threshold をキャプチャ → クロージャが生成される
    var filtered = numbers.Where(n => n > threshold);
}

頻繁に呼ばれるメソッドでは、このアロケーションが積み重なってガベージコレクションの負荷になります。

静的ラムダで回避

キャプチャが不要な場合は、静的ラムダを使うことでアロケーションを防げます。

var numbers = new List<int> { 1, 2, 3, 4, 5 };

// 静的ラムダ:キャプチャなし、アロケーションなし
var doubled = numbers.Select(static x => x * 2);
キャプチャありラムダ

呼び出しごとにクロージャオブジェクトが生成される可能性

静的ラムダ / キャプチャなし

デリゲートインスタンスがキャッシュされ、再利用される

デリゲートのキャッシュ

キャプチャのないラムダ式は、コンパイラが自動的にデリゲートをキャッシュしてくれます。

// キャプチャなし → デリゲートがキャッシュされる
Func<int, int> square = x => x * x;

何度呼び出しても同じデリゲートインスタンスが再利用されます。

LINQ のパフォーマンス

LINQ は便利ですが、パフォーマンスが重要な場面では従来のループのほうが効率的なこともあります。

var numbers = Enumerable.Range(1, 1000000).ToList();

// LINQ:簡潔だがオーバーヘッドあり
var sumLinq = numbers.Where(n => n % 2 == 0).Sum();

// ループ:冗長だが高速
int sumLoop = 0;
foreach (var n in numbers)
{
    if (n % 2 == 0)
        sumLoop += n;
}

大量のデータを処理する場合は、実測して判断することが重要です。

ボックス化に注意

ジェネリックでないデリゲートや、値型をキャプチャする際にボックス化が発生することがあります。

ボックス化とは

値型(int, struct など)をオブジェクト型として扱う際に発生するヒープ割り当てのこと。

回避策

可能な限りジェネリックなデリゲート(Func<T> など)を使用する。

実用的なガイドライン

パフォーマンスを意識する場合の指針をまとめます。

ホットパスではキャプチャを避けるか、静的ラムダを使う
大量データの処理では LINQ と手動ループを比較検討する
実測なしに最適化しない(プロファイリングが重要)
可読性とのバランスを考慮する

ほとんどのケースでは、ラムダ式のオーバーヘッドは問題になりません。パフォーマンスが本当に問題になった箇所だけを最適化するのが、現実的なアプローチです。