-
Notifications
You must be signed in to change notification settings - Fork 0
MS_LambdaExpression
ラムダ式っておじさん達にはユースケースがよく解りませんでしたが、
Java 8 でもラムダ式がサポートされたようなので、私を含めて、
「そろそろ、ラムダ式をちゃんと覚えよう。」
という人向けのコンテンツを書いてみました。
おじさん達の知識で振り返って分析してみると、
中水準言語の C 言語の関数ポインタから、
高水準言語の .NET 言語(C#、VB)で、
- デリゲート
- 匿名メソッド
- ラムダ式
と進化してきていることぐらいは解る(?)と思います。
これを見ると、
- デリゲート~ラムダ式の歴史を辿って | もりひろゆきの日々是勉強
http://moriblog.kit-eng.com/?p=975
厳密には、以下のように進化してきています。
補足(「関数を値として渡す」という一本の線): 原文の見立ての通り、
この進化は 「処理そのものを、変数のように受け渡ししたい」
という一つの要求に対する、書き方の簡略化の歴史である。C 言語 関数ポインタ 型安全でない。インスタンスと結び付かない ↓ C# 1.0 デリゲート 型安全。インスタンス メソッドも持てる ↓ ただし「名前付きメソッド」が必要 C# 2.0 匿名メソッド その場に処理を書ける ↓ ただし delegate キーワードと型指定が必要 C# 3.0 ラムダ式 型推論が効き、記号だけで書ける意味は変わっておらず、短くなっただけである。
実際、ラムダ式はコンパイル後には
**デリゲート(または式木)**になる。
同じシグネチャを持つメソッドを格納するための変数を宣言し、それを利用できる。
デリゲートの代入時にインスタンス生成(new デリゲート名)を省略できる。
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/.NET Framework 3.5)
-
ジェネリックの機能を用いて、3種類のデリゲートが定義され、
「delegate キーワードを使ったデリゲート宣言」を省略できる。-
定義済みデリゲート
ラムダ式で利用可能なメソッドを定義するために、
最新の .NET 4.5 では Action と Func の、T1-T16 までのデリゲートが用意されている。- Action<T1,T2,…,T16> Delegate (System)
https://learn.microsoft.com/ja-jp/dotnet/api/system.action-16 - Func<T1,T2,…,T16,TResult> Delegate (System)
https://learn.microsoft.com/ja-jp/dotnet/api/system.func-17
- Action<T1,T2,…,T16> Delegate (System)
-
-
参考
- Tech Tips: C#入門(9)Action、Func、Predicateの違い
http://techtipshoge.blogspot.com/2014/06/c9actionfuncpredicate.html - やさしいC# 3.0まとめblog: 【ラムダ式】 定義済みデリゲート一覧
http://csharp30matome.seesaa.net/article/129993518.html
- Tech Tips: C#入門(9)Action、Func、Predicateの違い
移行メモ(「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, ...>として使う、
といった代入が可能である。
定義済みデリゲート型とセットで使用する。
- デリゲート型 / 式木(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.htmlC# 言語のラムダ式(lambda expressions)とは、
デリゲート(delegate)や、メソッド・ベースの LINQ 文の(例えば)
Where メソッドや Select メソッドなどの引数をシンプルに記述するために、
C# 3.0(=Visual C# 2008)以降で導入された言語仕様である。
-
これについては、以下の利用例を引き合いに出して説明したいと思います。
移行メモ(式形式とステートメント形式が逆): 原文では
「式形式」に{ }付きの記法が、
「ステートメント形式」に{ }なしの記法が割り当てられているが、
C# の用語としては逆である。
- 式形式(expression lambda) …
x => x * 2({ }なし。値を返す)- ステートメント形式(statement lambda) …
x => { return x * 2; }以下では、原文の記載を保ちつつ正しい対応に入れ替えて掲載する。
- 引数がない場合
() => (式)- 引数が一つの場合
引数名 => (式)- 引数が複数の場合
(引数名1, 引数名2, 引数名3, ...) => (式)- 引数がない場合
() => {
(処理の実装)
}- 引数が一つの場合
(引数名) => {
(処理の実装)
}- 引数が複数の場合
(引数名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)を指定する。
- デリゲートをメソッドの引数として渡す場合で、
- 上記のデリゲートの引数をメソッドが内部から指定する場合
このため、一瞬どこから現れたのか不明な変数に見える。
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は「これから渡ってくる値の名前」**であり、
名前は何でもよい(x、item、userなど)。
上記以外の場合は、実引数を指定する。
ラムダ式では、そのスコープの外側にある
変数やメソッドを利用でき引数の肥大化が防げる。
(これは、匿名メソッドでも同じ)
補足(クロージャの落とし穴): この「外側の変数を使える」性質を
**クロージャ(変数の捕捉)**と呼ぶ。便利だが、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,3C# 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 to Objects などは匿名メソッド・匿名関数としてのラムダ式を引数にとる。
- LINQ to SQL、LINQ to Entities などは式木(Expression Tree)としてのラムダ式を引数にとる。
-
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) を参照。
- 関数型プログラミングって何、ラムダってなんだよ
http://qiita.com/lrf141/items/98ffbeaee42d30cca4dc - 【LINQの前に】ラムダ式?デリゲート?Func<T, TResult>?な人へのまとめ【知ってほしい】
https://qiita.com/RyotaMurohoshi/items/740151bd772889cf07de
- 匿名メソッド
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/delegate-operator - ラムダ式
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/lambda-expressions
- C#&VB ラムダ式 基礎文法★チートシート
http://www.atmarkit.co.jp/fdotnet/chushin/cheatsheet_01/cheatsheet_01_01.html
- Chapter16 LINQとメソッド構文 - @IT
http://www.atmarkit.co.jp/fdotnet/extremecs/extremecs_16/extremecs_16_01.html - Enumerable メソッド (System.Linq)
https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable
Tags: 移行, プログラミング, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。