【C#】ラムダ式をちゃんと理解したい
以前からUnityで開発していて時々出てきてはふわっとした理解のまま放置していた「ラムダ式」についていい加減本気で向き合いたい。
とりあえず一般的なラムダ式という概念について。
概念
---Javaのサイトから引用
Javaのラムダ式(lambda expression)とは、関数型インターフェイスを実装したクラスのインスタンスを、ごく短いコーディング量でとても簡単に作れてしまう文法のことです。
---C++のサイトから引用
「ラムダ式(lambda expressions)」は、簡易的な関数オブジェクトをその場で定義するための機能である。この機能によって、「高階関数(関数を引数もしくは戻り値とする関数)」をより使いやすくできる。
---C#のサイトから引用
"ラムダ式" を使用して匿名関数を作成します。 ラムダ宣言演算子=>を使用して、ラムダのパラメーター リストを式本体から分離します。 ラムダ式は、次の 2 つの形式のいずれかにすることができます。
---Wikipediaのそれっぽいページから引用
プログラミング言語における無名関数(英語: anonymous functionあるいはnameless function)[1]とは、名前付けされずに定義された関数のことである。無名関数を表現するための方法には様々なものがあるが、近年[2]主流となっているのはラムダ式による記法である。無名関数を表現するリテラル式は、関数リテラル (function literal) とも呼ばれる。値がある場合は関数オブジェクトであるものが多い。
Wikipedia上はラムダ式って言葉ではなく無名関数という名前で載っていた。おそらくITの理論的な言い方では正式にはそうなんだろう。
とにかく特徴として、
①関数をその場で作りますよ
②名前はないですよ
というのがざっくりした理解でよいと思った。
ラムダ式のメリット
まずラムダ式は必須ではない、けど使うとメリットがあるらしい。
・ラムダ式使わない場合
int Add(int x, int y)
{
return x + y;
}
・ラムダ式使う場合
(x, y) => x + y;
だいぶ縮んだ。引用だけど、「ラムダ式は関数をサクッと手短に書くために、無駄なタイプ量を減らすために徹底的に簡略化された記法」とのこと。
型は定義不要なんだけど、型推論で勝手に判断してるらしい。
上記例は引数として(int x, int y)があったけど、引数ない場合。
・ラムダ式使わない場合
private void Do()
{
Console.WriteLine("Do!");
}
・ラムダ式使う場合
() => Console.WriteLine("Do!");
縮んだ感はある。
実際の使用例
実際にどんな時に使うのか。
public void Start()
{
// 5秒経ったら関数を呼ぶ
// _ は"discard"を意味(await漏れによる警告抑制)
_ = WaitForAsync(5);
}
private async Task WaitForAsync(float seconds)
{
await Task.Delay(TimeSpan.FromSeconds(seconds));
Do();
}
private void Do()
{
Console.WriteLine("Do!");
}
こんなとき、ラムダ式(とデリゲート)を使うと、
public void Start()
{
// 5秒経ったら関数を呼ぶ
_ = WaitForAsync(5, () => Console.WriteLine("Do!"));
}
private async Task WaitForAsync(float seconds, Action action)
{
await Task.Delay(TimeSpan.FromSeconds(seconds));
action();
}
こうなる。
Startが呼び出すWaitForAsyncの中でやりたい処理をStartの中に記述できるので、見た瞬間に本筋の処理がどんななのかわかる。
元のやつだと関数を追っていかないとわからない。
極論を言ったら、
_ = WaitForAsync(5, () => Console.WriteLine("Do!"));
これを見ただけで、5秒待ってからWriteLineだなってわかる。
やるやん。
デリゲート
ラムダ式と切っては離せないらしいデリゲートについてもまとめた。
デリゲートを使うと、「登録する関数を自由に差し替えて、実行時に異なる関数を実行する」という処理が簡単に実装できます。
この説明だけだと、は?と思った。
たとえば、さきほどのWaitForAsyncもデリゲート(Action型)を使っています。
>このように、「関数の引数をデリゲートにする」ことで、「関数から別の関数を呼び出す」という処理を外から切り替えることができるようになります。
はーなるほどね。完璧に理解した。
デリゲートの使い方について。
C#の歴史上、デリゲートにはいろんな種類が存在します。ですが今日においてはジェネリック版のデリゲート(ActionとFunc)しかほぼ使いません。
デリゲートのパターン
①Action 引数なし、返り値なし
なんかの関数を実行するよって時に使う。
②Action<T> 引数あり、返り値なし
引数を渡してなんかの関数を実行するよって時に使う。
コメント渡して、そのコメントを送信とかね。
引数の数が増えたらこう。 Action<T1, T2, T3>
③Func<TResult> 引数なし、返り値あり
なんかの関数を実行してその結果を使いたいときに使う。
Func<int>って定義してたらint型の返り値が来る。
④Func<T, TResult> 引数あり、返り値あり
引数を渡してなんかの関数を実行して返り値何かに使うよって時に使う。
引数の数が増えたらこう。 Func<T1, T2, T3, TResult>
ラムダ式とデリゲート
ラムダ式を単体で使うことはなく、ラムダ式で生成した関数は必ずデリゲートに代入して使うとのこと。
private int Add(int x, int y)
{
return x + y;
}
public void Start()
{
// 関数をデリゲートに登録
Func<int, int, int> func1 = Add;
// ラムダ式で作った関数を登録
Func<int, int, int> func2 = (x, y) => x + y;
// 呼び出し
Console.WriteLine(func1(10, 20));
Console.WriteLine(func2(10, 20));
}
func2っていう関数の変数に、ラムダ式でお手軽に描いた足し算の関数を入れているね。
いろんな引数を使って同じような処理したいときは通常の方法で実現できるけど、いろんな関数を使って同じような処理したいときはぜんーぶ書くしかないと思ってたけど上手くラムダ式使えば省略できそうです。
てか、ほぼほぼこれ見てやってたからこの記事いらんわ。