【C#】delegate、Action、Func、匿名メソッド、ラムダ式のざっくりとした使い方

記事タイトルの項目について理解が甘かったため、勉強してまとめました。
# 動作確認環境
C# 7.3
.Net Framework 4.7.2

# delegate
関数への参照を表す型のこと。
デリゲート型の変数を使用することで、参照している関数を呼び出すことができる。

```CSharp
// 参照したい関数と同じ戻り値・引数のデリゲートを定義
private delegate void DelegateType(int i);

static void Main()
{
    // デリゲートのインスタンスを作成
    DelegateType func = new DelegateType(DisplayNum);

    // DisplayNum(1)を呼び出したことになる
    func(1);
}

// 引数の数値をコンソールに表示
private static void DisplayNum(int i)
{
    Console.WriteLine($"引数の値は {i} です");
}
```
インスタンス作成の行は、以下のような書き方も可能です。
こちらの方が直感的に理解できるのではないでしょうか。
```CSharp
// 参照したい関数と同じ戻り値・引数のデリゲートを定義
private delegate void DelegateType(int i);

static void Main()
{
    // デリゲートのインスタンスを作成
    DelegateType func = DisplayNum;

    // DisplayNum(1)を呼び出したことになる
    func(1);
}

// 引数の数値をコンソールに表示
private static void DisplayNum(int i)
{
    Console.WriteLine($"引数の値は {i} です");
}
```

「int num = 1;」とした場合にnumを1として扱えるのと同様、「DelegateType func = DisplayNum;」とすればfuncをDisplayNum関数として扱えます。

以下は戻り値を返す場合の例。
```CSharp
private delegate int DelegateType2(string str);

 static void Main()
 {
     DelegateType2 func = GetStrLen;

     // GetStrLen("abc")を呼び出したことになる
     int len = func("abc");
     Console.WriteLine($"文字数は {len} です");
 }

 // 引数の文字列の文字数を返却する
 private static int GetStrLen(string str)
 {
     return str.Length;
 }
```

:::note warn
一つのデリゲート型の変数で複数の関数を参照することもできますが、この記事では割愛します。
:::

# Action
簡略化したdelegate。
引数は指定できるが、戻り値は指定できない。

以下はdelegateの項のソースと同じ結果になります。
```CSharp
static void Main()
{
    // 「Action<参照する関数の引数1, 引数2, ...>」のように定義する
    Action<int> func = DisplayNum;

    // DisplayNum(1)を呼び出したことになる
    func(1);
}

// 引数の数値をコンソールに表示
private static void DisplayNum(int i)
{
    Console.WriteLine($"引数の値は {i} です");
}
```

ちなみにActionは以下のように定義されています。
```CSharp
public delegate void Action<in T>(T obj);
```
戻り値無しのデリゲートを、.Net Framework側が「Action」という名前で事前に用意してくれているということですね。
そのためdelegateの項の「DelegateType」のように、わざわざ自前で実装する必要がありません。

なお、Actionの引数は以下の通り0～16個の範囲で指定可能です。
```CSharp
public delegate void Action();
public delegate void Action<in T>(T obj);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
：
public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
```

# Func
簡略化したdelegate。
Actionと違い、戻り値も指定可能。

```CSharp
static void Main()
{
    // 「Func<参照する関数の引数1, 引数2, ... , 参照する関数の戻り値>」のように定義する
    Func<string, int> func = GetStrLen;

    // GetStrLen("abc")を呼び出したことになる
    int len = func("abc");
    Console.WriteLine($"文字数は {len} です");
}

// 引数の文字列の文字数を返却する
private static int GetStrLen(string str)
{
    return str.Length;
}
```

こちらも定義を見てみましょう。
Actionと違い、戻り値としてTResultが追加されていますね。
```CSharp
public delegate TResult Func<in T, out TResult>(T arg);
```
FuncもActionと同様、引数を0～16個の範囲で指定可能です。
```CSharp
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T, out TResult>(T arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3);
：
public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
```


# 匿名メソッド
参照するタイミングで定義する関数。
一箇所でしか参照しないような関数や、簡単な関数の場合、わざわざ事前に定義せずこの方法を使用したりする。

以下はAction、Funcの項のソースと同じ結果になります。
参照する関数の定義を、インスタンス作成行の右辺に移動しています。
```CSharp
static void Main()
{
    Action<int> func = delegate (int i) { Console.WriteLine($"引数の値は {i} です"); };
    
    func(1);
}
```
```CSharp
static void Main()
{
    Func<string, int> func = delegate (string str) { return str.Length; };

    int len = func("abc");
    Console.WriteLine($"文字数は {len} です");
}
```
匿名メソッドは「delegate (関数の引数) {関数の処理}」のような記述をします。
関数名が無い以外は通常の関数と同様、つまり「名前のない関数」＝「匿名メソッド」です。

# ラムダ式
簡略化した匿名メソッド。

こちらもAction、Funcの項のソースと同じ結果になります。匿名メソッドよりも更に関数定義が簡単になっています。
```CSharp
static void Main()
{
    Action<int> func = (int i) => { Console.WriteLine($"引数の値は {i} です"); };
    
    func(1);
}
```
```CSharp
static void Main()
{
    Func<string, int> func = (string str) => { return str.Length; };

    int len = func("abc");
    Console.WriteLine($"文字数は {len} です");
}
```

更に関数定義を省略することができます。
ネット上ではこちらの書き方をよく見かけます。
```CSharp
static void Main()
{
    Action<int> func = i => Console.WriteLine($"引数の値は {i} です");
    
    func(1);
}
```
```CSharp
static void Main()
{
    Func<string, int> func = str => str.Length;

    int len = func("abc");
    Console.WriteLine($"文字数は {len} です");
}
```

ラムダ式の使用例。
```CSharp
static void Main()
{
    // 非同期処理
    Task.Run(() => { Console.WriteLine("非同期処理です"); });
    

    // LINQなど
    var lst = new List<int> { 9, 99, 999 };
    
    // 10以上の値の要素のみ取り出して、新しいリストを作成
    // 要素ごとにWhereの引数の関数を実施し、trueを返すならばその要素を取り出す
    var newLst = lst.Where((int num) => { return num >= 10; }).ToList();
    //var newLst = lst.Where(num => num >= 10).ToList();    // 省略形
    
    // 新しいリストの各要素の値をコンソールに表示
    // 要素ごとにForEach引数の関数を実施する
    newLst.ForEach((int num) => { Console.WriteLine($"要素の値は {num} です"); });
    //newLst.ForEach(num => Console.WriteLine($"要素の値は {num} です"));    // 省略形
}
```

# まとめ
![図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3902550/61a0172b-b1cb-e367-5345-20e9d8ee7895.png)

# 参考URL
[【C#】delegate, Action, Funcについて 多分一番易しい解説](https://hikotech.net/post-449/)
[Action\<T\> Delegate (System) | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=netframework-4.7.2)
[Func\<T,TResult\> Delegate (System) | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/api/system.func-2?view=netframework-4.7.2)
[ラムダ式 - ラムダ式と匿名関数 - C# reference | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/lambda-expressions)