C# でラムダ式を使ってみる

# C# でラムダ式を使ってみる

こんにちは、@studio_meowtoon です。今回は、WSL Ubuntu 環境の C# でラムダ式を使い、その理解を深めていきます。  
![csharp_on_ubuntu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/23c7c71f-e5f1-3bb8-45b6-60ad19e41238.png)

## 技術トピック

### ラムダ式 とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### ラムダ式 (Lambda式)
ラムダ式は、プログラミング言語において、無名の関数を表現するための記法です。

:::note info

|特徴|内容|
|:--|:--|
|無名関数| ラムダ式は、関数の定義を名前なしで表現します。通常の関数は名前が必要ですが、ラムダ式は直接式として定義されるため、名前を持たずに使用することができます。|
|簡潔な記法| ラムダ式は、一つの式で表現されます。通常、関数の定義には複数の行や文が必要ですが、ラムダ式では一つの式で処理を記述することができます。|
|関数として扱える| ラムダ式は、変数に代入したり、引数として渡したり、関数の戻り値として使用したりすることができます。他の関数と同様に扱うことができるため、関数型プログラミングの一部として利用されます。|
|クロージャ| ラムダ式は、自身が定義されたスコープにある変数を参照することができます。これにより、ラムダ式内で外部の変数を利用することができます。この特徴をクロージャと呼びます。|

:::
</details>

:::note alert
C# には本来[厳格なコーディング規則](https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/coding-style/coding-conventions)がありますが、この記事では可読性のために、一部規則に沿わない表記方法を使用しています。ご注意ください。
:::

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

.NET SDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/d68031c91462f6808dff)

```sh
$ dotnet --list-sdks
7.0.202 [/usr/share/dotnet/sdk]
$ dotnet --version
7.0.202
```

</details>

## ラムダ式について

### ラムダ式の書き方
※C#での記法の一例  

```sh
(入力パラメータ) => 式
```

### 無名関数
※C#では匿名関数と呼ばれます。  

```csharp
(x, y) => x + y;
```

これは x, y を引数として x + y の計算結果を返す無名関数です。

### 無名関数とは

:::note info
関数名を記述せずに引数や代入に使用する関数です。
:::

以下に、名前がないのにどうやって使うのかという利用例を示します。

### 関数リテラルの構文

無名関数を変数に代入しています。

```csharp
Func<int, int, int> add = (x, y) => x + y;
AreEqual(5, add(2, 3));
```

### なぜ無名関数が必要なのか？

::: note info
今日においては無名関数の使用を前提としたライブラリなどが多くそれらを理解・活用するためにも必要です。また、無名関数は、柔軟性、可読性、保守性を向上させるために必要な機能です。特に関数型プログラミングやイベント駆動型のプログラミング、非同期処理などの場面で重要な役割を果たしています。
:::

### C# の LINQ ライブラリで引数にラムダ式を渡す例

+ LINQ の `Select`メソッドの引数にラムダ式を渡しています。

```csharp
List<int> src = new() {0, 1, 2, 3, 4, 5};
List<int> exp = new() {0, 2, 4, 6, 8, 10};
src = src.Select(x => x * 2).ToList();
CollectionAssert.AreEqual(exp, src);
```

+ ラムダ式で入力値を2倍して返す無名関数を定義しています。

```csharp
x => x * 2;
```

このように C# で LINQ を使用する為にはラムダ式の理解は必須となっています。

### ラムダ式の特徴
※ここではC#でのラムダ式の特徴を記します。

1. 変数に代入できる
1. 関数の引数に渡せる
1. 関数の戻り値にできる

#### `1. 変数に代入できる`の例

```csharp
Func<int, int> doubleValue = x => x * 2;
AreEqual(4, doubleValue(2));
```

|内容|
|:--|
|引数を2倍するラムダ式を `doubleValue` 変数に代入しています。|
|C# では `doubleValue` 変数をデリゲート型で定義しています。|
|`doubleValue` 変数に `(2)` と引数を渡して実行しています。|

#### `2. 関数の引数に渡せる`の例

```csharp
// 関数(メソッド)定義
int doValueTwo(Func<int, int> func) {
    return func(2);
}
// 使用例
AreEqual(4, doValueTwo(x => x * 2));
```

|内容|
|:--|
|`Func` デリゲート型の変数を引き数に取る `doValueTwo()` 関数(メソッド)を定義しています。|
|`doValueTwo()` 関数の引数にラムダ式を渡して実行しています。|
|`doValueTwo()` 関数内で引き数の `func` 変数が `(2)` と引数を渡されて実行されています。|

#### `3. 関数の戻り値にできる`の例①

```csharp
// 関数(メソッド)定義
Func<int> toDoubleValue(int value) {
    return () => value * 2;
}
// 使用例
AreEqual(4, toDoubleValue(2)());
```

|内容|
|:--|
|`Func` デリゲート型を戻り値に返し数値を引数に取る `toDoubleValue()` 関数(メソッド)を定義しています。|
|`toDoubleValue()` 関数の引数に `2` を渡し実行しています。|
|`toDoubleValue()` 関数内で `Func` 型の戻り値としてラムダ式が返されます。|
|`Func` 型の戻り値の変数を `()` と引数なしで実行しています。|

#### `3. 関数の戻り値にできる`の例②

```csharp
// 関数(メソッド)定義
Func<int, int> toDoubleValue2() {
    return value => value * 2;
}
// 使用例
AreEqual(4, toDoubleValue2()(2));
```

|内容|
|:--|
|`Func` デリゲート型を戻り値に返す `toDoubleValue2()` 関数(メソッド)を定義しています。|
|`toDoubleValue2()` 関数を引数なしで実行しています。|
|`toDoubleValue2()` 関数内で `Func` 型の戻り値としてラムダ式が返されます。|
|`Func` 型の戻り値の変数に `(2)` と引数を渡して実行しています。|

### ラムダ式の形式
※ここではC#でのラムダ式の特徴を記します。

1. 式形式のラムダ
1. ステートメント形式のラムダ

#### 1. 式形式のラムダ

```sh
(入力パラメータ) => 式
```

```csharp
Func<int, int, int> add = (x, y) => x + y;
AreEqual(5, add(2, 3));
```

+ ラムダ式で記述した無名関数を`add`変数に代入しています。
+ ラムダ式は右辺を`式形式`で記述しています。

#### 2. ステートメント形式のラムダ

```sh
(入力パラメータ) => ステートメントブロック
```

```csharp
Func<int, int, int> add = (x, y) => {
    return x + y;
};
AreEqual(5, add(2, 3));
```

+ ラムダ式で記述した無名関数を`add`変数に代入しています。
+ ラムダ式は右辺を`ステートメントブロック形式`で記述しています。

### ラムダ式を使用する例
C# でラムダ式を使用するライブラリの使用例を紹介します。  

#### LINQ: Where(), Select() メソッドでラムダ式を使う例

```csharp
List<Fruit> src = new() {
    new Fruit("りんご", 100), new Fruit("みかん", 160),
    new Fruit("メロン", 350), new Fruit("ぶどう", 250)
};
List<string> exp = new() {"ミカン", "ブドウ"};
var ret = src
    .Where(x => isHiragana(x.Name) && x.Price > 150)
    .Select(x => toKatakana(x.Name))
    .ToList();
CollectionAssert.AreEqual(exp, ret);
```

+ 150円以上のひらがな名の果物の名前をカタカナに変換する処理とします。
+ 真偽値を返す `isHiragana()`、ひらがなをカタカナに変換する `toKatakana()` 関数が定義されているとします。
+ LINQ の `Where()`: 値のフィルタリング、`Select()`: 値の射影を行います。

#### LINQ: Where(), Select() メソッドでラムダ式を使う例

+ LINQ + ラムダ式を使用しない記述に置き換えてみます。

```csharp
var ret = src
    .Where(x => isHiragana(x.Name) && x.Price > 150)
    .Select(x => toKatakana(x.Name))
    .ToList();
```
↓

### ラムダ式を使用しない例

```csharp
List<string> ret = new();
foreach (var fruit in src) {
    if (isHiragana(fruit.Name) && fruit.Price > 150) {
        ret.Add(toKatakana(fruit.Name));
    }
}
```

:::note warn
このように、`foreach(for)` や、`if` などの**制御構文**が直接プログラムコードに現れます。 別の言い方では `LINQ` と `ラムダ式` で記述することにより、**制御構文を隠蔽**することが可能です。
:::

## まとめ
+ C# でラムダ式を使い、記述方法やその動作について理解を深めることができました。 

どうでしたか？ Window 11 の WSL Ubuntu に、C# / .NET の開発環境を手軽に構築することができます。ぜひお試しください。今後も .NET の開発環境などを紹介していきますので、ぜひお楽しみにしてください。