Skip to content

MS_LambdaExpression

nishi_74322014 edited this page Sep 1, 2026 · 1 revision

ラムダ式って

概要

ラムダ式っておじさん達にはユースケースがよく解りませんでしたが、

Java 8 でもラムダ式がサポートされたようなので、私を含めて、

「そろそろ、ラムダ式をちゃんと覚えよう。」

という人向けのコンテンツを書いてみました。

ラムダ式

進化

おじさん達の知識で振り返って分析してみると、
中水準言語の C 言語の関数ポインタから、

高水準言語の .NET 言語(C#、VB)で、

と進化してきていることぐらいは解る(?)と思います。

これを見ると、

厳密には、以下のように進化してきています。

補足(「関数を値として渡す」という一本の線): 原文の見立ての通り、
この進化は 「処理そのものを、変数のように受け渡ししたい」
という一つの要求に対する、書き方の簡略化の歴史である。

C 言語         関数ポインタ            型安全でない。インスタンスと結び付かない
  ↓
C# 1.0         デリゲート              型安全。インスタンス メソッドも持てる
  ↓                                    ただし「名前付きメソッド」が必要
C# 2.0         匿名メソッド            その場に処理を書ける
  ↓                                    ただし delegate キーワードと型指定が必要
C# 3.0         ラムダ式                型推論が効き、記号だけで書ける

意味は変わっておらず、短くなっただけである。
実際、ラムダ式はコンパイル後には
**デリゲート(または式木)**になる。

デリゲート(C# 1.0)

同じシグネチャを持つメソッドを格納するための変数を宣言し、それを利用できる。

デリゲート(C# 2.0)

デリゲートの代入時にインスタンス生成(new デリゲート名)を省略できる。

匿名メソッド(C# 2.0)

delegate 構文を用いて「メソッド名を持たない処理」を記述できる。

  • イベント・ハンドラの例
// Create a handler for a click event.
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };
  • スレッド関数の例
System.Threading.Thread t1 = new System.Threading.Thread
  (delegate()
    {
        System.Console.Write("Hello, ");
        System.Console.WriteLine("World!");
    });
t1.Start();

定義済みデリゲート型(C# 3.0)

移行メモ(「3種類」の内訳): 原文の「3種類のデリゲート」とは、
次の 3 つを指す。

戻り値 用途
Action<...> なし(void) 実行するだけ
Func<..., TResult> あり 値を返す(LINQ の主役)
Predicate<T> bool 条件判定(Func<T, bool> と実質同じ)

Predicate<T>List<T>.FindAll などの古い API で使われるが、
LINQ は Func<T, bool> を使うため、
現在は Func に統一して考えてよい。

なお、Action / Func は
**in / out の変性(variance)**を持つため、
Func<object, ...>Func<string, ...> として使う、
といった代入が可能である。

ラムダ式(C# 3.0)

定義済みデリゲート型とセットで使用する。

  • デリゲート型 / 式木(Expression Tree)を作成する作法
  • ラムダ演算子(=>) を用いて左辺にパラメタ、右辺に式やコードブロックを置く

補足(その後の進化/最新化): 原文は C# 3.0 時点で止まっているが、
ラムダ式はその後も拡張されている。主なものは次の通り。

バージョン 内容
C# 6.0 式形式メンバー=> をメソッド定義自体に使う)
C# 7.0 ローカル関数(ラムダの代替。アロケーションが発生しない
C# 9.0 静的ラムダstatic x => ...。変数の捕捉を禁止して事故防止)
C# 10 ラムダの戻り値型指定・属性付与、自然な型(var f = () => 1;
C# 12 既定の引数値(int x = 5) => x * 2

static ラムダは実務上とくに有用で、
「意図せず外側の変数を捕捉して、寿命が延びる/確保が増える」
という事故をコンパイル時に防げる。

// 外側の変数を捕捉しないことを保証する
list.Where(static x => x > 5);

また、性能が要る場面では、
ラムダよりローカル関数の方が有利なことがある
(変数を捕捉しなければクロージャ オブジェクトが作られないため)。

用途

ラムダ式は、

  • LINQ クエリ式や
  • デリゲートと多用する新しい API の

delegate を必要とするメソッドを
実行しやすくするために生まれてきたようです。

  • 参考

    • C#ラムダ式 基礎文法最速マスター - @IT
      http://www.atmarkit.co.jp/fdotnet/rapidmaster/rapidmaster_01/rapidmaster_01.html

      C# 言語のラムダ式(lambda expressions)とは、
      デリゲート(delegate)や、メソッド・ベースの LINQ 文の(例えば)
      Where メソッドや Select メソッドなどの引数をシンプルに記述するために、
      C# 3.0(=Visual C# 2008)以降で導入された言語仕様である。

これについては、以下の利用例を引き合いに出して説明したいと思います。

ポイント

文法

移行メモ(式形式とステートメント形式が逆): 原文では
「式形式」に { } 付きの記法が、
「ステートメント形式」に { } なしの記法が割り当てられているが、
C# の用語としては逆である。

  • 式形式(expression lambda)x => x * 2{ } なし。値を返す)
  • ステートメント形式(statement lambda)x => { return x * 2; }

以下では、原文の記載を保ちつつ正しい対応に入れ替えて掲載する。

式形式(expression lambda)

  • 引数がない場合
() => ()
  • 引数が一つの場合
引数名 => ()
  • 引数が複数の場合
(引数名1, 引数名2, 引数名3, ...) => ()

ステートメント形式(statement lambda)

  • 引数がない場合
() => {
  (処理の実装)
}
  • 引数が一つの場合
(引数名) => {
  (処理の実装)
}
  • 引数が複数の場合
(引数名1, 引数名2, 引数名3, ...) => {
  (処理の実装)
}

補足(この違いは実務上重要): 見た目の問題に留まらない。
式木にできるのは式形式だけである。

// OK:式形式なので Expression にできる
Expression<Func<int,bool>> e1 = x => x > 5;

// NG:ステートメント形式は Expression にできない(コンパイル エラー)
Expression<Func<int,bool>> e2 = x => { return x > 5; };

このため、Entity Framework の Where の中に { } を書くと
意図せず動かない
、という現象が起きる。

仮引数と実引数

仮引数(parameter)

以下の場合、仮引数(parameter)を指定する。

  • デリゲートをメソッドの引数として渡す場合で、
  • 上記のデリゲートの引数をメソッドが内部から指定する場合

このため、一瞬どこから現れたのか不明な変数に見える。

LINQ のメソッド ベースのクエリ構文などで使用される。

補足(p => ...p はどこから来るのか): 原文が
「一瞬どこから現れたのか不明な変数に見える」と言っているのは、
ラムダ式を初めて読む人が最もつまずく点である。

list.Where(p => p.Age > 20)

この p自分では代入していない
Where の内部で、要素を 1 件ずつ渡しながら呼び出しているからである。

// Where の中では、概念的にこう動いている
foreach (var item in list)
    if (predicate(item))   // ← ここで item が p になる
        yield return item;

つまり **p は「これから渡ってくる値の名前」**であり、
名前は何でもよい(xitemuser など)。

実引数(argument)

上記以外の場合は、実引数を指定する。

スコープ

ラムダ式では、そのスコープの外側にある
変数やメソッドを利用でき引数の肥大化が防げる。

(これは、匿名メソッドでも同じ)

補足(クロージャの落とし穴): この「外側の変数を使える」性質を
**クロージャ(変数の捕捉)**と呼ぶ。便利だが、2 つの罠がある。

① 捕捉されるのは「値」ではなく「変数そのもの」

var actions = new List<Action>();
for (int i = 0; i < 3; i++)
    actions.Add(() => Console.WriteLine(i));
actions.ForEach(a => a());   // C# 4 以前 → 3,3,3

C# 5.0 で foreach の変数はループごとに新しくなるよう
仕様変更されたが、for 文は現在も上記のままである
(回避するにはループ内でローカル変数にコピーする)。

② 寿命が延びる

捕捉された変数はコンパイラが生成するクロージャ オブジェクト
移されるため、ラムダが生きている限り解放されない。
長寿命のイベント ハンドラーに登録すると、
意図しないメモリ リークの原因になる。

捕捉を避けたい場合は前述の static ラムダ(C# 9.0)を使う。

利用例

ということで、引数をラムダ式にすることで、

delegate を必要とするメソッドを実行しやすくする。

ようなシーンが、ラムダ式の代表的な利用ケースではないか?と思います。

  • (プログラミング C# - 翔ソフトウェア (Sho's))

    • [C#][ラムダ式][LINQ][式木] 匿名メソッドとラムダ式の違い
      http://blog.shos.info/archives/2012/11/clinqexpression.html

      ラムダ式を使った方が、型推論の恩恵を存分に受けられ、書き方がぐっとシンプルになる。

    • [C#][ラムダ式][匿名メソッド] ラムダ式は常に匿名メソッドよりシンプルに書ける?
      http://blog.shos.info/archives/2012/12/c_4.html

      しかし、ごく稀にだが、ラムダ式よりも匿名メソッドの方がシンプルになることがある。

  • ASP.NET MVC で WebGrid を最大限に活用する - MSDN
    https://msdn.microsoft.com/ja-jp/magazine/hh288075.aspx

    新しい WebGrid<T> 実装では、format パラメーターに
    Func<T, object> を受け取る新しい Column メソッドを追加しています。

    grid.Column(
      columnName: "EmailAddress", 
      header: "Email Address", 
      format: item => Html.EmailLink(
        (string)item.EmailAddress, 
        (string)item.EmailAddress, 
        ""
      ), 
      canSort: false
    )

補足(引数の型を書かなくてよい理由): 匿名メソッドが
delegate(System.Object o, System.EventArgs e)
型を明記していたのに対し、ラムダ式は (o, e) => ... で済む。

これは、代入先のデリゲート型から引数の型を推論しているためで、
逆に言えば「代入先が決まっていないラムダ式」は書けない
(C# 10 の「自然な型」で緩和されたが、限定的)。

LINQ での利用

  • LINQ のメソッド ベースのクエリ構文で使用される
    (LINQ プロバイダでは、ラムダ式を式として扱う)。

  • SELECT や WHERE 句などの動作のカスタマイズに一般的にはラムダ式を使用して記述する。

var query5 = RowList
  .Where(p => p.Field<int>("年度") == 2012)
  .Select(p => new
  {
    年度 = p.Field<int>("年度"),
    種別 = p.Field<int>("種別"),
    生年度 = p.Field<int>("生年度"),
    平均人員2 = p.Field<int>("平均人員") / 12,
    支給年金額 = p.Field<int>("支給年金額")
  });

移行メモ(誤記): 原文の「SELECTやWHEHE句」は
WHERE 句の誤記と判断し修正した。

補足: この「同じ Where でも、渡すのがデリゲートか式木かで
動作が根本的に変わる
」という点が LINQ 最大の勘所であり、
詳細は 式木(Expression Tree) を参照。

参考

Qiita

C# プログラミング ガイド

@IT

LINQ


Tags: 移行, プログラミング, .NET開発

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally