しらいしさん…

linuxに1000回くらい殺されてるしとりまmicrosoft様をageてくわ

時代の変遷から見るラムダ式の使い方

なんだよラムダ式って…ってなったしぱっっと見どう考えても意味が分からないけど、C#の「なるべく文章量は少なくかつ反射的に読めるようにする」の正統進化ポケモンみたいな感じなんだな

全体的に何

「メソッドを引数として渡したい」場合。返り値のあるメソッドであればそんなことは考えなくていいけど、voidなどで行う分岐・抽出系の処理でしばしばそういうことがある。普通にメソッド呼び出しすればいいんじゃない…?って話だけど、引数として使えるので、もう入力時の便利さが違う。毎回()を入力しなくていい。

サンプルコード

「5, 3, 9, 6, 7, 5, 8, 1, 0, 5, 10, 4」の中から、偶数のみを抽出(手法:n % 2 == 0)する。

偶数判定をするjudgeメソッドを、引数としてCountメソッドで利用したい。

デリゲート式

public delegate bool Judgement(int value);

public int Count(int numbers, Judgement judge)
{
int count = 0;
foreach (var n in numbers)
{
if (judge(n) == true)
{
count++;
}
}
return count;
}

public void Do()
{
var numbers = new { 5, 3, 9, 6, 7, 5, 8, 1, 0, 5, 10, 4 };
//judgementの中身の処理としてIsEvenを代入
Judgement judge = IsEven;
var count = Count(numbers, judge);
Console.WriteLine(count);

}

public bool IsEven(int n)
{
return n % 2 == 0;
}

 public delegate 返り値 メソッド名(引数)」がほぼ魔法の呪文。

型はメソッド名の変数が宣言できて、その中身としたいメソッドを変数形式で直接代入できる。

Judgement judge = IsEven;

 で引数にしたいデリゲート「Judgement」に中身を代入して、

var count = Count(numbers, judge);

 で中身を持ったJudgementを引数として指定することができる。

 

ただしこれ、わざわざIsEvenメソッドスコープを作成して中身をコーディングしちゃってるので、労力がそんなに削減できてない。

 

匿名メソッド

private int Count(int numbers, Predicate<int> judge)
{
int count = 0;
foreach (var n in numbers)
{
if (judge(n) == true)
{
count++;
}
}
return count;
}

public void Do()
{
var numbers = new { 5, 3, 9, 6, 7, 5, 8, 1, 0, 5, 10, 4 };
var count = Count(numbers, delegate (int n) { return n % 2 == 0; });
Console.WriteLine(count);
}

 メソッドスコープ作成はやめよう、という感じでこうなった。分量は確かに減った気がする。

 

ラムダ式

private int Count(int numbers, Predicate<int> judge)
{
int count = 0;
foreach (var n in numbers)
{
if (judge(n) == true)
{
count++;
}
}
return count;
}

public void Do()
{
var numbers = new { 5, 3, 9, 6, 7, 5, 8, 1, 0, 5, 10, 4 };
var count = Count(numbers, n => n % 2 == 0);
Console.WriteLine(count);
}

 所要分量10文字以内の限界に挑戦、成功。

 public delegate 返り値 メソッド名(引数)」はやっぱり魔法の呪文。(ただし、deligateでない場合もある)

var count = Count(numbers, n => n % 2 == 0); 

 「n(とりあえず任意の変数) => 処理文」が定型文。なんだこのn…って感じで初見はビビリが走るけど、ただのメソッドの引数。

基本的には勝手に型推論をしてくれるので、型宣言も必要ない。*1

処理に対して引数が複数ある場合は、引数部分を()で囲む。「(n,s) =>」って感じ。引数がない場合、「() =>」みたいな書き方もできるけど、フレームワークが限定されるしあんまり今回の趣旨にも合わないし覚えにくいし読みにくいしなので推奨しない。普通にメソッドを使おう。

 

 

ラムダ式の有効活用

単純に処理を表記する他にも色々お手軽な使い方があって、たとえば

var country = new List<string>{"Tokyo", "New Delhi", "Bangkok", "London", "Paris", "Berlin", "Canberra", "Hong Kong"};

って感じのリストがあるとして、以下の使い方ができる。

List<T>クラスのメソッドを利用する

//文字「A」がリストに含まれるかどうかを判定する*2

ver exists = country.Exists(s => s[0] =='A');

Console.WriteLine(exists)

これの問題は、手軽だけどほぼList<T>でしか利用できないという点。他の配列系要素でいろいろしたい場合は、ちょっと手間と分量がかかるけどIEnumarable<T>(ほぼすべての配列要素系に適用できる)下のLINQを使う。

LINQのwhere句、select句(クエリ演算子)の具体的な条件・処理として利用する

where句は、その名の通り要素抽出の条件にするクエリ演算子。単純に要素の抽出で済ませたいのであれば、ここに書くだけでよい。

//5文字以下の国名のみ抽出する。*3

IEnumerable<string> query = country.Where(s => s.Length <= 5);

select句は、引数として渡ってきた要素に対し、ラムダ式で指定した変換処理をするクエリ演算子。もちろん単体で使う事もできるし、where句とかの後ろ(改行はしたほうがよさそう)に.で続けてwhere句で抽出した要素に対して処理を行う使い方が割と基本な気がする。*4

//5文字以下の国名のみ抽出して、抽出したものを全て小文字に変換する*5

IEnumerable<string> query = country.Where(s => s.Length <= 5)

                                               .Select(s => s.ToLower());

その他にも、クエリ演算子は山ほどあったりするので、後々調べよう(本の説明がよくわからなかったので若干詰み感がある)

forEachの短縮

戻り値の型はdeligate<T>ではなく、Action<T>。『T型の引数を一つとり返値が無い関数』、ほぼvoidってこと。分量が増えるくらいならこれにしてもいい気がする。ただし、間に何らかの処理を入れる場合、普通にforEachにしたほうが分かりやすいか?と思う。

//listの内容を全て表示する

country.forEach(s => Console.WriteLine(s));

 

けっこう頑張って書いたね。LINQはこれ単体で書籍が出るほどの沼らしいので、まあ、やっていきましょうって感じ。やっていきましょう

*1:ただし間違われたり難しかったりする場合もあるにはあるので、複数だったり複雑な処理の場合は「int n」というように型宣言しておくとよい

*2:大文字のAが含まれる国名は一件もないので、この場合falseがWriteLineされる

*3:Tokyo、Parisが抽出される。

*4:こうして.でクエリ演算子やメソッドを結合することを「メソッドチェーン」というらしいよ。なんか調べるとメソッドチェーン大好きマンがいっぱいいるわ。怖いわ

*5:tokyo、parisが抽出される。