C#のラムダ式とLinqついて

C#のラムダについて勉強したことをまとめる。
Java8での場合についても記載する。

#1. C#のラムダまでの歴史

以下の例題でラムダへの経緯について記述する。

例題：Stringの配列に対して、文字列の長さでデータを抽出する。

```cs:ラムダ無し
        private void button2_Click(object sender, EventArgs e)
        {
            var values = new string[] { "a", "bb", "ccc", "dddd", "eeeee" };
            var result = GetData(values);
            Console.WriteLine(string.Join(",", result));
        }

        private string[] GetData(string[] values)
        {
            var result = new List<string>();
            foreach (var val in values)
            {
                if (val.Length > 2)
                {
                    result.Add(val);
                }
            }
            return result.ToArray();
        }
```

```text:実行結果
ccc,dddd,eeeee
```

上記の例ではサイズが2より大きいものを抽出しているが、3以下の場合を抽出したい場合に、対応できない。
「if (val.Length > 2)」の2を引数にすることはできるが、以上、以下、などの等号を変更することができない。

## 1-1.delegate(デリゲート)

```cs:delegate

        private void button2_Click(object sender, EventArgs e)
        {
            var values = new string[] { "a", "bb", "ccc", "dddd", "eeeee" };
            var result = GetData(values, Check1);
            Console.WriteLine(string.Join(",", result));
        }

        private string[] GetData(string[] values, LengthCheck check)
        {
            var result = new List<string>();
            foreach (var val in values)
            {
                if (check(val))
                {
                    result.Add(val);
                }
            }
            return result.ToArray();
        }

        delegate bool LengthCheck(string value);

        bool Check1(String value)
        {
            return value.Length > 2;
        }
```

delegateを使うことにより式を渡すことができるようになった。
そのため、3以下を抽出したい場合は以下の用にCheck2メソッドを作成して、それを渡せばよい。

```cs:delegate2
       private void button2_Click(object sender, EventArgs e)
        {
            var values = new string[] { "a", "bb", "ccc", "dddd", "eeeee" };
            var result = GetData(values, Check2);
            Console.WriteLine(string.Join(",", result));
        }

        private string[] GetData(string[] values, LengthCheck check)
        {
            var result = new List<string>();
            foreach (var val in values)
            {
                if (check(val))
                {
                    result.Add(val);
                }
            }
            return result.ToArray();
        }

        delegate bool LengthCheck(string value);

        bool Check1(String value)
        {
            return value.Length > 2;
        }

        bool Check2(String value)
        {
            return value.Length <= 3;
        }
```

## 1-2.匿名メソッド

上の例では、メソッドを作る必要があり、メソッド名を考える必要がある。
また、呼び出している「GetData(values, Check2)」と実際の宣言が離れているので、何をやっているかがわかりにくいという問題がある。

```cs:匿名メソッド
        private void button2_Click(object sender, EventArgs e)
        {
            var values = new string[] { "a", "bb", "ccc", "dddd", "eeeee" };
            var result = GetData(values, 
                delegate(string s) { return s.Length <= 3; }) ;
            Console.WriteLine(string.Join(",", result));
        }

        private string[] GetData(string[] values, LengthCheck check)
        {
            var result = new List<string>();
            foreach (var val in values)
            {
                if (check(val))
                {
                    result.Add(val);
                }
            }
            return result.ToArray();
        }

        delegate bool LengthCheck(string value);
```

その問題を解決したのが匿名メソッド。匿名メソッドは.NET 2.0で導入された。

## 1-3.Predicate(プレディケート)

.NET 2.0でジェネリックが導入され、その結果、bool Predicate\<in T>を使えばdelegateの宣言も不要になった。

```cs:Predicate
        private void button2_Click(object sender, EventArgs e)
        {
            var values = new string[] { "a", "bb", "ccc", "dddd", "eeeee" };
            var result = GetData(values, 
                delegate(string s) { return s.Length <= 3; }) ;
            Console.WriteLine(string.Join(",", result));
        }

        private string[] GetData(string[] values, Predicate<string> check)
        {
            var result = new List<string>();
            foreach (var val in values)
            {
                if (check(val))
                {
                    result.Add(val);
                }
            }
            return result.ToArray();
        }
```

## 1-4.ラムダ式

匿名メソッドで「delegate(string s) { return s.Length <= 3; }」と書いていたのをラムダ演算子「=>]
で書くと以下の用になる。

```cs:ラムダ式
        private void button2_Click(object sender, EventArgs e)
        {
            var values = new string[] { "a", "bb", "ccc", "dddd", "eeeee" };
            var result = GetData(values, s => s.Length <= 3) ;
            Console.WriteLine(string.Join(",", result));
        }

        private string[] GetData(string[] values, Predicate<string> check)
        {
            var result = new List<string>();
            foreach (var val in values)
            {
                if (check(val))
                {
                    result.Add(val);
                }
            }
            return result.ToArray();
        }
```
ラムダ式は.NET 3.0で導入された。
ラムダ演算子は「Goes to」(ゴーズ・トゥ)と読む。
左側がパラメータで右側が式or文

## 1-5.おまけ

今回のサンプルについては、以下のコードでも同じ結果になる

```cs:おまけ
        private void button2_Click(object sender, EventArgs e)
        {
            var values = new string[] { "a", "bb", "ccc", "dddd", "eeeee" };
            var result = values.Where(s => s.Length <= 3);
            Console.WriteLine(string.Join(",", result));
        }

```


# 2.ラムダ式の文法

## 2-1.パラメータ(「=>」の左側)

### パラメータの型は書かなくて良い。
以前は「delegate(string s) { return s.Length <= 3; })」のように「delegate」と「string」を書いていたが不要になった。
「GetData(values, s => s.Length <= 3)」の第二パラメータは「private string[] GetData(string[] values, Predicate<string> check)」とstring型であることがわかるため。

なお、以下の用に書いてもエラーにはならない。

```cs:型を明記してもOK
GetData(values, (string s) => s.Length <= 3) ;
```

### パラメータの数によって書式が異なる。
パラメータが0個の場合は()が必要。

```cs:パラメータ0個
() => hoge
```

パラメータが1個の場合は()は不要。あってもOK。

```cs:パラメータ1個
s => hoge
```

パラメータが複数の場合は()で囲ってカンマで区切る。

```cs:パラメータ複数個
（a,b) => hoge
```

## 2-2.式or文(「=>」の右側)

### 式のみの場合はreturnが不要

```cs:return不要
GetData(values, s => s.Length <= 3) ;
```

### 文の場合は{}の中に記述し、returnが必要

```cs:returnあり
GetData(values, s => { return s.Length <= 3; }) ;
```

# 3.FuncとAction

Funcは戻り値あり、Actionは戻り値なし。

Func\<T,T,Tresult>で山かっこの最後が戻り値の型。パラメータの数は上限が16個。
Funcが出来たので、Predicateは使わなくなった。

## 3-1.Action

重たいメソッドに対して、開始と終了のログを出力して、非同期で動かすサンプル。

```cs:Actionサンプル
        private void button2_Click(object sender, EventArgs e)
        {
            Wrap(Execute);
        }

        private void Execute()
        {
            Console.WriteLine("重たい処理実行");
            System.Threading.Thread.Sleep(5000);
        }

        private async void Wrap(Action action)
        {
            Console.WriteLine("START");
            await Task.Run(() => action());
            Console.WriteLine("END");
        }
```

# 4.int型のリストに対しての処理

## 4-1.抽出とソート

```cs:C#
        private void button2_Click(object sender, EventArgs e)
        {
            int[] values = new int[] { 2, 5, 3, 1, 8, 4, 2, 6 };
            // 5以上の値を昇順で抽出
            var result = values.Where(v => v >= 5).OrderBy(v => v);
            Console.WriteLine(string.Join(",", result));
        }
```

```java:Java
import java.util.Arrays;
import java.util.stream.IntStream;

public class Sample {

	public static void main(String[] args) {
		int[] values = new int[] { 2, 5, 3, 1, 8, 4, 2, 6 };
		// 5以上の値を昇順で抽出
		IntStream stream = Arrays.stream(values);
		stream.filter(v -> v >= 5).sorted().forEach(v -> System.out.print(v + " "));
	}
}

```

## 4-2.Max,Min,Average

```cs:C#
        private void button2_Click(object sender, EventArgs e)
        {
            int[] values = new int[] { 2, 5, 3, 1, 8, 4, 2 };
            var max = values.Max();
            var min = values.Min();
            var avg = values.Average();
            Console.WriteLine($"Max={max}, Min={min}, Average={avg}");
        }
```
```java:Java
import java.util.Arrays;

public class Sample {

	public static void main(String[] args) {
		int[] values = new int[] { 2, 5, 3, 1, 8, 4, 2, 6 };
		int max = Arrays.stream(values).max().getAsInt();
		int min = Arrays.stream(values).min().getAsInt();
		double avg = Arrays.stream(values).average().getAsDouble();
		System.out.println(String.format("max=%d, min=%d, average=%f", max, min, avg));
	}
}
```

#5.遅延実行
Linqは遅延実行だが、ちょっとわかりにくくてバグに繋がる可能性もあるので整理する。

サンプルとして、１～１０の数値を５倍して、偶数だけを抽出し、その合計を出すプログラムを作成した。

##5-1.遅延実行
```cs:C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Class1
    {
        internal static void Main(string[] args)
        {
            int[] numbers = Enumerable.Range(1, 10).ToArray();

            IEnumerable<int> number5 = numbers.Select(n =>
            {
                Console.WriteLine($"{n}を5倍にします。");
                return n * 5;
            });

            Console.WriteLine("5倍処理終了？");

            IEnumerable<int> even = number5.Where(n =>
            {
                Console.WriteLine($"{n}を偶数か判定します。");
                return n % 2 == 0;
            });

            Console.WriteLine("偶数かの判定終了？");

            int sum = even.SumNumber();
            Console.WriteLine($"合計は{sum}");
        }
    }

    public static class SumSample
    {
        public static int SumNumber(this IEnumerable<int> numbers)
        {
            Console.WriteLine("Sum処理開始");
            int sum = 0;
            foreach(var n in numbers)
            {
                sum += n;
                Console.WriteLine($"{n}を足します。(sum:{sum})");
            }
            Console.WriteLine("Sum処理終了");
            return sum;
        }
    }
}
```
```text:結果
5倍処理終了？
偶数かの判定終了？
Sum処理開始
1を5倍にします。
5を偶数か判定します。
2を5倍にします。
10を偶数か判定します。
10を足します。(sum:10)
3を5倍にします。
15を偶数か判定します。
4を5倍にします。
20を偶数か判定します。
20を足します。(sum:30)
5を5倍にします。
25を偶数か判定します。
6を5倍にします。
30を偶数か判定します。
30を足します。(sum:60)
7を5倍にします。
35を偶数か判定します。
8を5倍にします。
40を偶数か判定します。
40を足します。(sum:100)
9を5倍にします。
45を偶数か判定します。
10を5倍にします。
50を偶数か判定します。
50を足します。(sum:150)
Sum処理終了
合計は150
```

ループの中でConsoleに出力するようにしているが、「最初に値を５倍にする処理を行って、それが終わってから抽出する処理を行って・・・」という動きになっていない。
SumNumber処理で必要になってから処理が実行されている。（遅延実行）

##5-2.即時実行
```cs:C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Class1
    {
        internal static void Main(string[] args)
        {
            int[] numbers = Enumerable.Range(1, 10).ToArray();

            IEnumerable<int> number5 = numbers.Select(n =>
            {
                Console.WriteLine($"{n}を5倍にします。");
                return n * 5;
            }).ToArray();   // 即時実行

            Console.WriteLine("5倍処理終了？");

            IEnumerable<int> even = number5.Where(n =>
            {
                Console.WriteLine($"{n}を偶数か判定します。");
                return n % 2 == 0;
            }).ToArray();   // 即時実行

            Console.WriteLine("偶数かの判定終了？");

            int sum = even.SumNumber();
            
            Console.WriteLine($"合計は{sum}");
        }
    }

    public static class SumSample
    {
        public static int SumNumber(this IEnumerable<int> numbers)
        {
            Console.WriteLine("Sum処理開始");
            int sum = 0;
            foreach(var n in numbers)
            {
                sum += n;
                Console.WriteLine($"{n}を足します。(sum:{sum})");
            }
            Console.WriteLine("Sum処理終了");
            return sum;
        }
    }
}

```

```text:結果
1を5倍にします。
2を5倍にします。
3を5倍にします。
4を5倍にします。
5を5倍にします。
6を5倍にします。
7を5倍にします。
8を5倍にします。
9を5倍にします。
10を5倍にします。
5倍処理終了？
5を偶数か判定します。
10を偶数か判定します。
15を偶数か判定します。
20を偶数か判定します。
25を偶数か判定します。
30を偶数か判定します。
35を偶数か判定します。
40を偶数か判定します。
45を偶数か判定します。
50を偶数か判定します。
偶数かの判定終了？
Sum処理開始
10を足します。(sum:10)
20を足します。(sum:30)
30を足します。(sum:60)
40を足します。(sum:100)
50を足します。(sum:150)
Sum処理終了
合計は150
```
即時実行するためにはToArrayやToListを付ければよい。

##5-3.遅延実行の注意点

直観的にわかりにくい。以下の例では2,4,6,8という値が入ったリストに対して5以上のものを抽出している。
6と8だけ抽出されるのではなく、そのあとに追加された7も抽出されるので注意。

```cs:C#
        private void button1_Click(object sender, EventArgs e)
        {
            List<int> list = new List<int> { 2, 4, 6, 8 };
            var result = list.Where(x => x > 5);
            list.Add(3);
            list.Add(7);
            Console.WriteLine(string.Join(",", result));
        }
```
```text:結果
6,8,7
```

## 5-4.遅延実行のメリット

### 5-4-1.メモリ使用量が少なくて済む

5-1にあるnumber5やevenは実体を持っていない。必要になったときに呼ばれて値を返すだけ。