C#のForm,WPFで Delegate, Action, Func, メソッドの定義 の違いをサンプルコードでざっくり理解する

## GUIを固めずに更新したい！

まずざっくりと概要。
Delegateとは、誤解を恐れず平たく言うと「メソッド集」です。
Delegateを使うシーンは、例えば、実行スレッドで処理中、GUIを固めないように、Label1のUIスレッドで実行したければ、以下のような感じにしたい。けど、以下の記述だとビルドエラーとなる。
Formアプリで記載していますが、WPFでは、Label等のコントロールのプロパティが違うのと、`[Control名].Invoke()`ではなく`Dispatcher.Invoke()`になるだけでほとんど同じ。
Invoke()はUIスレッドの完了を待ち、BeginInvoke()にすれば待たずに抜ける。

```C#
// やりたい事のイメージ
public static void Main() // 実行スレッド
{
    // メインスレッド等から、GUIを固めずにC#らしく更新したい。
    // !!! このコードは引数の型違いでビルドエラーになる !!!
    Label1.Invoke(Method1); // 同期的に実行 (GUI更新完了を待つ)
    Label1.Invoke(Method2, 0, 1); // 上記の引数付き
    Label1.BeginInvoke(Method1); // 非同期実行 (GUI更新完了を待たない)
    Label1.BeginInvoke(Method2, 0, 1); // 上記の引数付き
    // !!!
}

public void Method1()
{
    Label1.Text = "hoge";
}

public void Method2(int a, int b)
{
    Label1.Text = a.ToString() + b.ToString();
}
```

という感じで、「Action や Func とかが存在するのは知ってるんだけど、delegateはどうやるの？」みたいな時に、理解していないと使えない。

## Delegate とは

Delegate は、代理人という意味らしく、C++でいう関数ポインタなようなもの。
C#は、ポインタのようなunsafeな事をするような言語ではないので、代わりの手段が用意されていて、それがDelegate。

つまり、Delegateは「メソッド(関数)」ではなく「メソッド集」という感じ。
メソッドを１つだけ登録する事も出来るし、複数登録して順次実行させることもできる。
クラスのように定義してからそのあと実体化させる感じ。

```C#
public class MyClass
    // メソッド定義の外で予め Delegate を定義しておく必要あり
    delegate void MyDlgt(int value);
    
    public static void Main()
    {
        // Delegate (メソッド集) を実体化して、メソッドを追加
        var myDlgtInst = new MyDlgt(Method1);
        // 既存の Delegate にメソッドを追加
        myDlgtInst += new MyDlgt(Method2);
    }
```

`Delegate`は、イベントハンドラにメソッドを登録するときの`イベントハンドラ += メソッド()`みたいなイメージです。
（例：`Form1.Load += Form1_Load();`みたいなの。ちなみにあれは追加されるのは`EventHandler`型等です。Delegateの場合、自分で定義した型（戻り値、引数）のメソッドだけ追加できます。上記でいうと、`MyDlgt`型のDelegateを用意した場合、`void メソッド名(int)`の構造を持ったメソッドだけ登録可能。）

## Action, Func を使ってメソッドを delegate 型に変換する

`delegate`型が欲しい時、`Action`クラスと`Func`クラスを使って、メソッドを`delegate`型に変換した物を得る事が出来る。
`Action` と `Func` の違いは戻り値があるかどうか。引数はどちらも0～16個まで対応。

- 戻り値無し = `Action`
- 戻り値有り = `Func`

また、ラムダ式、匿名メソッドを使うと`() => { 処理1; 処理2; }`みたいな形で、別途メソッドを定義するのを省略して書ける。

```C#
// 戻り値無し、引数無し → Action
delegate void MyDlgt1();
// 戻り値無し、引数有り → Action<int, int> // 引数は0～16個まで対応
delegate void MyDlgt2(int value1, int value2);
// ※ Func は <> 内の一番最後が戻り値です。
// 戻り値有り、引数無し → Func<int>
delegate int MyDlgt3();
// 戻り値有り、引数有り → Func<int, int, int> // 引数は0～16個対応)
delegate int MyDlgt4(int value1, int value2);

public static void Main()
{
    // 引数バリエーション
    // (メソッドの型と Action, Func の型が一致している必要あり)
    Label1.Invoke(new Action(Method1)); // 戻り値無し、引数無し。void func();
    Label1.Invoke(new Action<int, int>(Method2); // 戻り値無し、引数有り。void func(int, int);
    // (Funcの <> 内の最後が戻り値の型指定)
    Label1.Invoke(new Func<int>(Method3)); // 戻り値有り、引数無し。int func();
    Label1.Invoke(new Func<int, int, int>(Method4)); // 戻り値有り、引数有り。int func(int, int);

    // メソッドを２つ以上登録する場合
    // (定義した delegete とメソッドの型が一致している必要あり)
    var dlgtInst1 = new MyDlgt1(Method1); // メソッド定義を 定義した delegate に変換
    dlgtInst1 += new MyDlgt1(Method1); // 2連続実行を登録
    Label1.Invoke(dlgtInst1);
    var dlgtInst2 = new MyDlgt2(Method2); // メソッド定義を 定義した delegate に変換
    dlgtInst2 += new MyDlgt2(Method2); // 2連続実行を登録
    Label1.Invoke(dlgtInst2);
    var dlgtInst3 = new MyDlgt3(Method3); // メソッド定義を 定義した delegate に変換
    dlgtInst3 += new MyDlgt3(Method3); // 2連続実行を登録
    Label1.Invoke(dlgtInst3);
    var dlgtInst4 = new MyDlgt4(Method4); // メソッド定義を 定義した delegate に変換
    dlgtInst4 += new MyDlgt4(Method4); // 2連続実行を登録
    Label1.Invoke(dlgtInst4);

    // 動作タイミングバリエーション
    Label1.Invoke(new Action(Method1)); // 同期的に実行 (UIスレッドの終了を待つ)
    Label1.BeginInvoke(new Action(Method1)); // 非同期実行 (UIスレッドの終了を待たずにすぐ抜けてくる)

    // ラムダ式
    // (匿名メソッドの定義が "delegate (引数) { // 処理 };" で、
    //  さらに delegate を省略(ラムダ式)し、改行を入れて複数行にして可読性を確保。
    //  戻り値は、推測可能な場合、型を記載しなくても指定した事になるそうです。
    //  逆にいうと、推測不可な場合は、ラムダ式では戻り値を書けないらしい。)
    // 引数無し。(void func() を登録)
    Label1.Invoke(new Action(() =>
    {
        Label1.Text = "hoge";
    }));
    // 引数有り。(void func(int, int) を登録)
    Label1.Invoke(new Action<int, int>((int value1, int value2) => 
    {
        Label1.Text = value1.ToString() + value2.ToString();
    }), 0, 1);
    // 引数無し、戻り値有り。(int func() を登録)
    // Invoke() の戻り値は object 型になってしまうので、(int)でキャストが必要
    // num = (int)Invoke(delegate); という形
    // (ちなみに1行で済むなら return の単語も省略可能)
    int num = (int)Label1.Invoke(new Func<int>(() =>
    {
        Label1.Text = "hoge";
        return 10;
    }));
    Debug.Print(num.ToString());
    // 引数有り、戻り値有り。(int func(int, int) を登録)
    // Invoke() の戻り値は object 型になってしまうので、(int)でキャストが必要
    // num = (int)Invoke(delegate, 0, 1); という形
    // (ちなみに1行で済むなら return の単語も省略可能)
    int num = (int)Label1.Invoke(new Func<int, int, int>((int value1, int value2) =>
    {
        Label1.Text = value1.ToString() + value2.ToString();
        return 10;
    }), 0, 1); // ここの 0, 1 は Invoke(delegate, args[]) への引数args部分
    Debug.Print(num.ToString()); // 10 が表示される。

}

void Method1()
{
    Label1.Text = "hoge";
}

void Method2(int value1, int value2)
{
    Label1.Text = value1.ToString() + value2.ToString();
}

int Method3()
{
    Label1Text = "hoge";
    return 1;
}

int Method4(int value1, int value2)
{
    Label1.Text = value1.ToString() + value2.ToString();
    return 1;
}

```

## とても参考にさせて頂いたサイト

https://hikotech.net/post-449/

https://atmarkit.itmedia.co.jp/fdotnet/extremecs/extremecs_07/extremecs_07_02.html

## 動作環境
Visual Studio 2017 (.NET Framework 4.6.1)

各バージョンとC#バージョンの対応はこちら等を参照

https://so-zou.jp/software/tech/programming/c-sharp/introduction/version.htm