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
型に変換した物を得る事が出来る。
Action
と Func
の違いは戻り値があるかどうか。引数はどちらも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#バージョンの対応はこちら等を参照