.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型であるのは、どのようなデリゲート型のインスタンスでも受け取ることができるようにするためだと思われる。End of Article

カテゴリ:C# 処理対象:言語構文
カテゴリ:クラス・ライブラリ 処理対象:コントロール
使用ライブラリ:Controlクラス(System.Windows.Forms名前空間)
使用ライブラリ:Threadクラス(System.Threading名前空間)
使用ライブラリ:ThreadStartデリゲート(System.Threading名前空間)
使用ライブラリ:Delegateクラス(System名前空間)
使用ライブラリ:MethodInvokerクラス(System.Windows.Forms名前空間)
関連TIPS:Windowsフォームで別スレッドからコントロールを操作するには?

この記事と関連性の高い別の.NET TIPS
汎用的に使用できる定義済みのデリゲート型は?
Windowsフォームで別スレッドからコントロールを操作するには?
文字列で指定したメソッドを呼び出すには?
タイマにより一定時間間隔で処理を行うには?(スレッドタイマ編)
数値のデータ型を明示的に指定するには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間