0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

GUIを固めずに更新したい!

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

// やりたい事のイメージ
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は「メソッド(関数)」ではなく「メソッド集」という感じ。
メソッドを1つだけ登録する事も出来るし、複数登録して順次実行させることもできる。
クラスのように定義してからそのあと実体化させる感じ。

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型に変換した物を得る事が出来る。
ActionFunc の違いは戻り値があるかどうか。引数はどちらも0~16個まで対応。

  • 戻り値無し = Action
  • 戻り値有り = Func

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

// 戻り値無し、引数無し → 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);

    // メソッドを2つ以上登録する場合
    // (定義した 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;
}

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

動作環境

Visual Studio 2017 (.NET Framework 4.6.1)

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?