|
.NET TIPS
ControlクラスのInvokeメソッドで匿名メソッドを使うには?[2.0のみ、C#]
デジタルアドバンテージ 遠藤 孝信
2007/10/18 |
|
|
C# 2.0では「匿名メソッド」と呼ばれる機能が追加され、それまで複数のメソッドに分けて記述しなければならなかった処理を1つにまとめることができる。
しかし、Controlクラス(System.Windows.Forms名前空間)のInvokeメソッドのパラメータとして匿名メソッドを記述するとコンパイル・エラーとなってしまう。
匿名メソッドの基本的な使い方
例えば、Threadクラス(System.Threading名前空間)により別のスレッドで処理を実行する場合、C# 1.xでは次のように記述していた。
void Run() {
ThreadStart ts = new ThreadStart(worker);
Thread t = new Thread(ts);
t.Start();
}
void worker() {
Console.WriteLine("別スレッドで実行中");
}
|
|
リスト1 別スレッドでのworkerメソッドの実行 |
上記のコードでは、Threadクラスはデリゲート・オブジェクト(変数ts)を経由してworkerメソッドを呼び出す。
ThreadStart型はデリゲート型であり、System.Threading名前空間で次のように定義されている。これは、workerメソッドのように戻り値がなく、パラメータも取らないメソッドに対して利用可能なデリゲート型である。
public delegate void ThreadStart();
|
|
リスト2 デリゲート型であるThreadStart型の定義 |
さて、C# 2.0の匿名メソッドを使えば、リスト1のコードは次のように1つのメソッドにまとめることができる。
void Run() {
Thread t = new Thread(
delegate() {
Console.WriteLine("別スレッドで実行中");
}
);
t.Start();
}
|
|
リスト3 匿名メソッドを使用してリスト1を書き換えたコード |
リスト1にあったworkerメソッドを匿名メソッドで置き換えている。 |
「delegate() {……}」の部分が匿名メソッドである。匿名メソッドはC# 2.0のコンパイラにより、デリゲート・オブジェクト(=デリゲート型のインスタンス)に置き換えられると考えればよいだろう。
デリゲート経由で呼び出されるメソッドの内容が簡単な場合(数行程度の場合)には、匿名メソッドを使うことによりコードをシンプルに記述できる。
System.Delegate型のパラメータを受け取るControl.Invokeメソッド
以上の例はスレッドの起動に関するものだが、Windowsフォーム・アプリケーションを作成していると、別スレッドで実行されているコードからフォーム上のコントロールを操作するために、Invokeメソッド*を使用した次のようなコードがよく必要となる。
* System.Windows.Forms名前空間のControlクラスのメソッド。フォームやすべてのコントロールはこのメソッドを継承している。Invokeメソッドを使うと、パラメータで指定したデリゲートのメソッドをメイン・スレッドで実行できる。
|
delegate void SetFocusDelegate();
void SetFocus()
{
// メイン・スレッドで実行したい処理
}
// このworkerメソッドは別スレッドで実行されるものとする
void worker()
{
// thisはフォームのインスタンスを参照しているものとする
this.Invoke(new SetFocusDelegate(SetFocus));
}
|
|
リスト4 Invokeメソッドの使用例 |
このコードは「TIPS:Windowsフォームで別スレッドからコントロールを操作するには?」からの引用である。どのような場面で必要になるかについては、そちらのTIPSを参照していただきたい。
もちろんC# 2.0ではこの場面でも匿名メソッドを利用できるが、先ほどと同様にして次のように書き換えるとコンパイル・エラーが発生する。
void worker()
{
// コンパイル・エラーとなる
Invoke(
delegate() {
// メイン・スレッドで実行したい処理
}
);
}
|
|
リスト5 匿名メソッドをパラメータに指定できないInvokeメソッド |
コンパイル時には次のような2つのエラー・メッセージが表示される。
'System.Windows.Forms.Control.Invoke(System.Delegate)' に最も適しているオーバーロード メソッドには無効な引数がいくつか含まれています。
'匿名メソッド' から 'System.Delegate' に変換できません。
|
|
リスト5をコンパイルしたときのエラー・メッセージ |
つまり、InvokeメソッドのパラメータはSystem.Delegate型でなければならないが、匿名メソッドはこの型へはキャストできないという内容のものである。
ではInvokeメソッドで匿名メソッドを利用するにはどうすればよいのだろうか? 結論から先にいうと、次のように適当なデリゲート型に匿名メソッドをキャストしておけばよい。
delegate void SetFocusDelegate();
void worker()
{
// コンパイルOK
Invoke(
(SetFocusDelegate)delegate() {
// メイン・スレッドで実行したい処理
}
);
}
|
|
リスト6 Invokeメソッドで匿名メソッドを使用する場合の記述例 |
System.Windows.Forms名前空間には、戻り値がなく、パラメータも取らないメソッドに対して利用可能なデリゲート型としてMethodInvoker型があらかじめ定義されている。Windowsフォーム・アプリケーションであれば、リスト6の場合にはこのMethodInvoker型を利用できる。
void worker()
{
// コンパイルOK
Invoke(
(MethodInvoker)delegate() {
// メイン・スレッドで実行したい処理
}
);
}
|
|
リスト7 MethodInvoker型を使用したInvokeメソッドによる匿名メソッドの使用 |
では、なぜこのようなキャストが必要なのであろうか?
デリゲート型でないSystem.Delegate型
上記の問題点は、次のような簡単なコードで表すことができる。このコードもコンパイル・エラーとなる。
using System;
class Program {
static void Main() {
Delegate d = delegate() { /**/ }; // コンパイル・エラー
// デリゲート型ではないため、匿名メソッド ブロックを
// 型 'System.Delegate' に変換できません
}
}
|
|
リスト8 コンパイル・エラーとなる匿名メソッドのDelegate型への代入 |
このエラーは、正確には、System.Delegate型はデリゲート型でないため、コンパイラは匿名メソッドの具体的な型を決めることができないという意味である。
匿名メソッドを記述する場合には、その具体的なデリゲート型が(匿名メソッドの外部の情報から)特定可能でなければならない。しかしSystem.Delegate型は抽象型であり、具体的なデリゲート型ではないため、リスト8はコンパイル・エラーとなる。
リスト8を正しくコンパイルできるようにするには、次のように匿名メソッドに対してキャストによる型の指定が必要となる。
using System;
using System.Windows.Forms;
class Program {
static void Main() {
Delegate d = (MethodInvoker)delegate() { /**/ }; // コンパイルOK
}
}
|
|
リスト9 匿名メソッドのDelegate型への代入 |
あるいはこの場合には、以下のように記述してもよい。
MethodInvoker d = delegate() { /**/ }; // コンパイルOK
|
|
また、コンパイラは、匿名メソッドが記述されているメソッドのパラメータの型からも、そのデリゲート型を特定できる(リスト10)。先ほどのリスト3がコンパイル・エラーとならないのは、この理由のためである。
using System;
using System.Windows.Forms;
class Program {
static void Main() {
InvokeMethodInvoker(delegate() { /**/ }); // コンパイルOK
}
static void InvokeMethodInvoker(MethodInvoker d) {
d();
}
}
|
|
リスト10 MethodInvoker型のパラメータを持つメソッド |
ちなみに、すべてのデリゲート型はSystem.Delegate型から派生している。InvokeメソッドのパラメータがSystem.Delegate型であるのは、どのようなデリゲート型のインスタンスでも受け取ることができるようにするためだと思われる。
カテゴリ:C# 処理対象:言語構文
カテゴリ:クラス・ライブラリ 処理対象:コントロール
使用ライブラリ:Controlクラス(System.Windows.Forms名前空間)
使用ライブラリ:Threadクラス(System.Threading名前空間)
使用ライブラリ:ThreadStartデリゲート(System.Threading名前空間)
使用ライブラリ:Delegateクラス(System名前空間)
使用ライブラリ:MethodInvokerクラス(System.Windows.Forms名前空間)
関連TIPS:Windowsフォームで別スレッドからコントロールを操作するには? |
|
generated by
|
|