C#のInvokeとは?使い方・必要な場面・エラー対処を初心者向けに徹底解説
はじめに
C#を学んでいると、「Invoke」という言葉を何度も見かけます。
たとえば、次のようなコードです。
C#action.Invoke();
C#this.Invoke((Action)(() =>
{
label1.Text = "更新しました";
}));
C#methodInfo.Invoke(obj, null);
同じ「Invoke」という名前でも、使われる場面によって意味や目的が少しずつ異なります。そのため、初心者の方は「結局Invokeって何?」「いつ使えばいいの?」「普通にメソッドを呼び出すのと何が違うの?」と混乱しやすいポイントです。
C#におけるInvokeは、基本的には「何かを呼び出して実行するための仕組み」です。ただし、デリゲートのInvoke、Windows FormsのControl.Invoke、WPFのDispatcher.Invoke、リフレクションのMethodInfo.Invokeなど、用途によって書き方や注意点が異なります。
この記事では、C#のInvokeについて、初心者にもわかりやすく意味・使い方・必要になる場面・よくあるエラーの対処法まで解説します。
1. C#のInvokeとは?初心者向けに意味をわかりやすく解説
1-1. Invokeは「呼び出す」という意味
Invokeは英語で「呼び出す」「実行する」「発動する」といった意味を持つ言葉です。
C#では、Invokeは主に「何らかの処理を呼び出して実行する」ために使われます。
たとえば、デリゲートに登録されたメソッドを実行する場合、次のように書けます。
C#Action action = () => Console.WriteLine("Hello");
action.Invoke();
このコードでは、actionに登録されている処理をInvoke()で実行しています。
実行結果は次のようになります。
C#Hello
つまり、Invoke()は「登録されている処理を呼び出す」という役割を持っています。
1-2. C#でInvokeが使われる主な3つの場面
C#でInvokeがよく使われる代表的な場面は、主に次の3つです。
1つ目は、デリゲートを実行するときです。
C#Action action = () => Console.WriteLine("処理を実行");
action.Invoke();
2つ目は、Windows FormsやWPFなどで、別スレッドからUIを更新するときです。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
3つ目は、リフレクションを使ってメソッドを動的に呼び出すときです。
C#methodInfo.Invoke(instance, parameters);
このように、同じInvokeでも「デリゲートの実行」「UIスレッドでの実行」「動的なメソッド呼び出し」など、使われる場面によって目的が変わります。
1-3. 通常のメソッド呼び出しとの違い
通常のメソッド呼び出しは、次のように直接メソッド名を書いて実行します。
C#SayHello();
void SayHello()
{
Console.WriteLine("Hello");
}
一方、Invokeを使う場合は、メソッドそのものを直接呼び出すのではなく、デリゲートやオブジェクトを通じて間接的に呼び出します。
C#Action action = SayHello;
action.Invoke();
void SayHello()
{
Console.WriteLine("Hello");
}
この例では、SayHelloメソッドをAction型の変数に代入し、その変数を通じてInvoke()しています。
通常のメソッド呼び出しは「このメソッドを直接実行する」という書き方です。一方、Invokeは「変数やオブジェクトに登録されている処理を呼び出す」というイメージです。
1-4. 初心者が混乱しやすい「Invoke」の種類
C#には、複数の種類のInvokeがあります。
代表的なものは次のとおりです。
C#delegate.Invoke()
これは、デリゲートに登録されたメソッドを実行します。
C#Control.Invoke()
これは、Windows FormsでUIスレッド上に処理を切り替えて実行します。
C#Dispatcher.Invoke()
これは、WPFでUIスレッド上に処理を切り替えて実行します。
C#MethodInfo.Invoke()
これは、リフレクションを使ってメソッドを動的に実行します。
どれも「呼び出す」という意味では共通していますが、目的が異なります。そのため、「Invoke」という名前だけで判断するのではなく、「何に対してInvokeしているのか」を見ることが重要です。
2. C#でInvokeが必要になる代表的な場面
2-1. デリゲートを実行するとき
C#でInvokeがもっとも基本的に使われる場面は、デリゲートを実行するときです。
デリゲートとは、メソッドを変数のように扱うための仕組みです。
C#delegate void MyDelegate();
MyDelegate myDelegate = () =>
{
Console.WriteLine("デリゲートを実行しました");
};
myDelegate.Invoke();
このように、デリゲート変数に登録された処理を実行するためにInvoke()を使います。
ただし、デリゲートは次のようにInvokeを省略して呼び出すこともできます。
C#myDelegate();
つまり、デリゲートにおけるInvoke()は明示的な呼び出し方法であり、通常は省略して書かれることも多いです。
2-2. Windows FormsでUIスレッドから処理を実行するとき
Windows Formsでは、画面上のコントロールは基本的にUIスレッドからしか操作できません。
たとえば、別スレッドで処理を実行している途中にLabelやTextBoxを更新しようとすると、エラーになることがあります。
C#label1.Text = "更新しました";
この処理をバックグラウンドスレッドから実行すると、次のような例外が発生することがあります。
C#InvalidOperationException
このような場合に、UIスレッドへ処理を切り替えるためにControl.Invoke()を使います。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新しました";
}));
Windows FormsでInvokeが必要になる代表的な場面は、「別スレッドから画面を更新したいとき」です。
2-3. WPFでDispatcher.Invokeを使うとき
WPFでも、Windows Formsと同じようにUI要素はUIスレッドから操作する必要があります。
WPFでは、UIスレッドへの処理の切り替えにDispatcher.Invoke()を使います。
C#Dispatcher.Invoke(() =>
{
textBlock.Text = "更新しました";
});
Windows FormsではControl.Invoke()、WPFではDispatcher.Invoke()を使う、と覚えると理解しやすいです。
どちらも「UIスレッド上で処理を実行する」という目的は同じです。
2-4. リフレクションでメソッドを動的に呼び出すとき
リフレクションを使うと、実行時に型情報やメソッド情報を取得し、動的にメソッドを呼び出せます。
C#MethodInfo method = typeof(Sample).GetMethod("Hello");
method.Invoke(sample, null);
通常のメソッド呼び出しでは、コンパイル時に呼び出すメソッドが決まっています。
C#sample.Hello();
一方、リフレクションでは、文字列でメソッド名を指定して実行できます。
C#typeof(Sample).GetMethod("Hello");
そのため、プラグイン機構、テストコード、フレームワーク、ライブラリなどで使われることがあります。
2-5. イベント処理でInvokeが使われる場面
C#のイベントでもInvokeはよく使われます。
たとえば、イベントを発火させるときに次のように書くことがあります。
C#public event EventHandler? Completed;
private void OnCompleted()
{
Completed?.Invoke(this, EventArgs.Empty);
}
このコードでは、Completedイベントに登録されているイベントハンドラーを呼び出しています。
?.Invoke()を使うことで、イベントに誰も登録していない場合でもエラーを防げます。
イベント処理におけるInvokeは、「イベントに登録された処理をまとめて呼び出す」ために使われます。
3. デリゲートのInvokeの基本的な使い方
3-1. デリゲートとは何か
デリゲートとは、メソッドを参照するための型です。
簡単にいうと、「メソッドを変数のように扱う仕組み」です。
たとえば、次のようなメソッドがあるとします。
C#void SayHello()
{
Console.WriteLine("Hello");
}
このメソッドをデリゲートに代入できます。
C#Action action = SayHello;
そして、次のように実行できます。
C#action.Invoke();
デリゲートを使うと、「あとで実行する処理を渡す」「処理を差し替える」「イベントとして通知する」といった柔軟な書き方ができます。
3-2. delegate.Invoke()の基本構文
デリゲートのInvokeの基本構文は次のとおりです。
C#デリゲート変数.Invoke(引数);
引数がない場合は、次のように書きます。
C#action.Invoke();
引数がある場合は、次のように書きます。
C#Action<string> action = message =>
{
Console.WriteLine(message);
};
action.Invoke("こんにちは");
戻り値がある場合は、Funcを使うことが多いです。
C#Func<int, int, int> add = (a, b) => a + b;
int result = add.Invoke(3, 5);
Console.WriteLine(result);
実行結果は次のようになります。
C#8
3-3. Invokeを省略して呼び出す書き方
デリゲートは、Invokeを省略して呼び出すこともできます。
C#Action action = () =>
{
Console.WriteLine("処理を実行");
};
action.Invoke();
これは、次のように書いても同じ意味です。
C#action();
引数がある場合も同じです。
C#Action<string> action = message =>
{
Console.WriteLine(message);
};
action("こんにちは");
戻り値がある場合も、Invokeを省略できます。
C#Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5);
実務では、デリゲートの呼び出しではInvoke()を省略する書き方もよく使われます。ただし、イベント発火では?.Invoke()の形がよく使われます。
3-4. Action・FuncでInvokeを使う例
C#では、デリゲートを自分で定義しなくても、ActionやFuncを使うことで簡単に処理を変数として扱えます。
Actionは戻り値がない処理を表します。
C#Action greet = () =>
{
Console.WriteLine("こんにちは");
};
greet.Invoke();
引数ありのActionも使えます。
C#Action<string> printMessage = message =>
{
Console.WriteLine(message);
};
printMessage.Invoke("メッセージを表示します");
Funcは戻り値がある処理を表します。
C#Func<int, int, int> multiply = (x, y) =>
{
return x * y;
};
int result = multiply.Invoke(4, 5);
Console.WriteLine(result);
実行結果は次のとおりです。
C#20
Actionは「実行するだけ」、Funcは「実行して結果を返す」と考えるとわかりやすいです。
3-5. nullチェックとNullReferenceExceptionを防ぐ書き方
デリゲート変数がnullの状態でInvokeすると、NullReferenceExceptionが発生します。
C#Action? action = null;
action.Invoke();
このコードは、actionがnullなのでエラーになります。
安全に呼び出すには、事前にnullチェックを行います。
C#Action? action = null;
if (action != null)
{
action.Invoke();
}
イベントでも同じです。
C#public event EventHandler? Completed;
private void OnCompleted()
{
if (Completed != null)
{
Completed(this, EventArgs.Empty);
}
}
ただし、C#ではより簡潔で安全な書き方として、?.Invoke()がよく使われます。
3-6. ?.Invoke()を使った安全な呼び出し方法
?.Invoke()は、対象がnullでない場合だけInvokeする書き方です。
C#Action? action = null;
action?.Invoke();
この場合、actionがnullでも何も実行されず、例外も発生しません。
イベント発火でもよく使われます。
C#public event EventHandler? Completed;
private void OnCompleted()
{
Completed?.Invoke(this, EventArgs.Empty);
}
この書き方は非常に一般的です。
Completedにイベントハンドラーが登録されていれば実行され、登録されていなければ何もしません。
初心者の方は、イベントを発火するときは次の形を覚えておくと便利です。
C#イベント名?.Invoke(this, EventArgs.Empty);
4. Windows FormsのControl.Invokeの使い方
4-1. Control.Invokeとは何か
Control.Invokeは、Windows FormsでUIスレッド上に処理を切り替えて実行するためのメソッドです。
Windows Formsでは、フォームやボタン、ラベル、テキストボックスなどのコントロールは、作成されたスレッドから操作する必要があります。
通常、UIコントロールはメインスレッドで作成されます。このメインスレッドをUIスレッドと呼びます。
別スレッドから直接UIを更新しようとすると、エラーが発生することがあります。そのため、UIスレッドで処理を実行するためにInvokeを使います。
4-2. UIスレッドとは何か
UIスレッドとは、画面表示やユーザー操作を処理するスレッドのことです。
Windows Formsアプリでは、フォームの表示、ボタンのクリック、テキストボックスの入力、ラベルの更新などは、基本的にUIスレッド上で処理されます。
たとえば、ボタンをクリックしたときのイベントハンドラーは、通常UIスレッドで実行されます。
C#private void button1_Click(object sender, EventArgs e)
{
label1.Text = "クリックされました";
}
このように、UIスレッド上でUIを更新する場合は問題ありません。
しかし、別スレッドで重い処理を実行して、その中からUIを更新しようとすると問題が起きます。
4-3. 別スレッドからUIを更新するとエラーになる理由
Windows FormsのUIコントロールは、スレッドセーフではありません。
スレッドセーフとは、複数のスレッドから同時にアクセスしても安全に動作する性質のことです。
UIコントロールが複数のスレッドから同時に変更されると、表示状態が壊れたり、予期しない動作になったりする可能性があります。
そのため、Windows Formsでは、UIコントロールを作成したスレッド以外から直接アクセスすると、次のような例外が発生することがあります。
C#InvalidOperationException:
有効ではないスレッド間の操作です。
この問題を避けるために、Control.Invokeを使ってUIスレッド上で処理を実行します。
4-4. Control.Invokeを使ったUI更新のサンプルコード
次のコードは、別スレッドからラベルを更新する例です。
C#using System;
using System.Threading.Tasks;
using System.Windows.Forms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
// 重い処理を想定
System.Threading.Thread.Sleep(1000);
// UIスレッドでラベルを更新
this.Invoke((Action)(() =>
{
label1.Text = "処理が完了しました";
}));
});
}
}
Task.Runの中は別スレッドで実行されます。その中から直接label1.Textを変更するとエラーになる可能性があります。
そこで、次の部分でUIスレッドに処理を渡しています。
C#this.Invoke((Action)(() =>
{
label1.Text = "処理が完了しました";
}));
これにより、ラベルの更新処理はUIスレッド上で安全に実行されます。
4-5. InvokeRequiredで呼び出しが必要か判定する方法
InvokeRequiredを使うと、現在のスレッドから直接UIを操作してよいかどうかを判定できます。
C#private void SetLabelText(string text)
{
if (label1.InvokeRequired)
{
label1.Invoke((Action)(() =>
{
label1.Text = text;
}));
}
else
{
label1.Text = text;
}
}
このコードでは、別スレッドから呼び出された場合はInvokeを使い、UIスレッドから呼び出された場合はそのまま更新します。
C#if (label1.InvokeRequired)
この条件がtrueなら、Invokeが必要です。
C#label1.Invoke((Action)(() =>
{
label1.Text = text;
}));
この条件がfalseなら、すでにUIスレッド上にいるため、直接更新できます。
C#label1.Text = text;
このようにメソッド化しておくと、どのスレッドから呼び出しても安全にUIを更新できます。
4-6. BeginInvokeとの違い
Invokeと似たものにBeginInvokeがあります。
Invokeは同期的に実行されます。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
この場合、UIスレッドで処理が完了するまで、呼び出し元のスレッドは待機します。
一方、BeginInvokeは非同期的に実行されます。
C#this.BeginInvoke((Action)(() =>
{
label1.Text = "更新";
}));
この場合、UIスレッドに処理を依頼したあと、呼び出し元のスレッドは処理の完了を待たずに次へ進みます。
簡単にいうと、次の違いがあります。
Invokeは「終わるまで待つ」。
BeginInvokeは「依頼して先に進む」。
UI更新だけを依頼したい場合は、BeginInvokeの方が適していることもあります。ただし、更新後の結果をすぐに使いたい場合はInvokeが必要になることもあります。
5. WPFのDispatcher.Invokeの使い方
5-1. Dispatcher.Invokeとは何か
Dispatcher.Invokeは、WPFでUIスレッド上に処理を切り替えて実行するためのメソッドです。
WPFのUI要素も、基本的にはUIスレッドから操作する必要があります。
たとえば、別スレッドからTextBlockの文字を変更しようとすると、エラーになることがあります。
C#textBlock.Text = "更新しました";
これを避けるために、WPFではDispatcher.Invokeを使います。
C#Dispatcher.Invoke(() =>
{
textBlock.Text = "更新しました";
});
Windows FormsのControl.Invokeと似ていますが、WPFではDispatcherを通じてUIスレッドに処理を渡す点が特徴です。
5-2. WPFでUIスレッドを扱う基本
WPFアプリでは、画面上のコントロールはDispatcherと関連付けられています。
Dispatcherは、UIスレッド上で実行する処理を管理する仕組みです。
ボタンやテキストボックスなどのUI要素は、基本的に作成されたUIスレッドからしか操作できません。
たとえば、ボタンのクリックイベント内でUIを更新する場合は問題ありません。
C#private void Button_Click(object sender, RoutedEventArgs e)
{
textBlock.Text = "クリックされました";
}
しかし、バックグラウンド処理からUIを更新する場合は、Dispatcherを使ってUIスレッドへ処理を渡す必要があります。
5-3. Dispatcher.Invokeを使った画面更新の例
次のコードは、WPFでバックグラウンド処理の完了後に画面を更新する例です。
C#private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
// 重い処理を想定
Thread.Sleep(1000);
// UIスレッドで画面を更新
Dispatcher.Invoke(() =>
{
textBlock.Text = "処理が完了しました";
});
});
}
Task.Runの中は別スレッドで実行されます。
そのため、次のように直接UIを更新するとエラーになる可能性があります。
C#textBlock.Text = "処理が完了しました";
そこで、次のようにDispatcher.Invokeを使います。
C#Dispatcher.Invoke(() =>
{
textBlock.Text = "処理が完了しました";
});
これにより、textBlock.Textの更新処理がUIスレッド上で実行されます。
5-4. Dispatcher.BeginInvokeとの違い
Dispatcher.Invokeは同期的に実行されます。
C#Dispatcher.Invoke(() =>
{
textBlock.Text = "更新";
});
この場合、UIスレッドで処理が完了するまで呼び出し元は待機します。
一方、Dispatcher.BeginInvokeは非同期的に処理を予約します。
C#Dispatcher.BeginInvoke(() =>
{
textBlock.Text = "更新";
});
BeginInvokeは、UIスレッドに処理を登録したあと、完了を待たずに次の処理へ進みます。
そのため、UI更新を依頼するだけでよい場合はBeginInvokeが使われることもあります。
ただし、現在のC#ではasync/awaitと組み合わせやすいDispatcher.InvokeAsyncが使われる場面も増えています。
5-5. async/awaitとDispatcher.InvokeAsyncの使い分け
WPFでは、async/awaitを使うことで、UIを固めずに非同期処理を書けます。
C#private async void Button_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Thread.Sleep(1000);
});
textBlock.Text = "処理が完了しました";
}
このようなコードでは、awaitの後にUIスレッドへ戻るため、明示的にDispatcher.Invokeを書かなくてもUIを更新できる場合があります。
ただし、バックグラウンドスレッド内からUI更新を行いたい場合は、Dispatcher.InvokeAsyncが便利です。
C#await Dispatcher.InvokeAsync(() =>
{
textBlock.Text = "更新しました";
});
InvokeAsyncはTaskとして扱えるため、awaitと相性がよいです。
初心者の方は、WPFでは次のように使い分けるとよいでしょう。
通常の非同期処理ではasync/awaitを使う。
バックグラウンドスレッドからUI更新が必要な場合はDispatcher.InvokeまたはDispatcher.InvokeAsyncを使う。
awaitしたい場合はDispatcher.InvokeAsyncを使う。
6. リフレクションのMethodInfo.Invokeの使い方
6-1. リフレクションとは何か
リフレクションとは、プログラムの実行時に型やメソッド、プロパティなどの情報を調べたり操作したりする仕組みです。
通常、C#ではメソッドを呼び出すときに、次のようにコンパイル時点でメソッド名を書きます。
C#sample.Hello();
一方、リフレクションを使うと、文字列でメソッド名を指定して呼び出すことができます。
C#MethodInfo? method = typeof(Sample).GetMethod("Hello");
method?.Invoke(sample, null);
このように、実行時にメソッドを探して呼び出せるのがリフレクションの特徴です。
6-2. MethodInfo.Invokeでメソッドを動的に呼び出す仕組み
MethodInfo.Invokeは、取得したメソッド情報を使って、そのメソッドを実行するためのメソッドです。
次のようなクラスがあるとします。
C#public class Sample
{
public void Hello()
{
Console.WriteLine("Hello");
}
}
このHelloメソッドをリフレクションで呼び出すには、次のように書きます。
C#using System;
using System.Reflection;
Sample sample = new Sample();
MethodInfo? method = typeof(Sample).GetMethod("Hello");
method?.Invoke(sample, null);
第1引数には、メソッドを実行する対象のインスタンスを指定します。
C#sample
第2引数には、メソッドに渡す引数を配列で指定します。引数がない場合はnullを指定できます。
C#null
6-3. 引数ありメソッドをInvokeするサンプルコード
引数があるメソッドをMethodInfo.Invokeで呼び出す場合は、第2引数にオブジェクト配列を渡します。
C#public class Calculator
{
public void PrintSum(int a, int b)
{
Console.WriteLine(a + b);
}
}
このメソッドをリフレクションで呼び出す例です。
C#using System;
using System.Reflection;
Calculator calculator = new Calculator();
MethodInfo? method = typeof(Calculator).GetMethod("PrintSum");
method?.Invoke(calculator, new object[] { 3, 5 });
実行結果は次のようになります。
C#8
引数の数や型が合っていないとエラーになります。
たとえば、引数が2つ必要なのに1つしか渡さないと、TargetParameterCountExceptionが発生する可能性があります。
6-4. 戻り値を取得する方法
戻り値があるメソッドをMethodInfo.Invokeで呼び出すと、戻り値はobject型として返されます。
C#public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
このAddメソッドを呼び出して戻り値を取得する例です。
C#using System;
using System.Reflection;
Calculator calculator = new Calculator();
MethodInfo? method = typeof(Calculator).GetMethod("Add");
object? result = method?.Invoke(calculator, new object[] { 3, 5 });
Console.WriteLine(result);
実行結果は次のとおりです。
C#8
戻り値をintとして扱いたい場合は、キャストします。
C#int value = (int)result!;
ただし、戻り値がnullになる可能性がある場合は、nullチェックも行うと安全です。
C#if (result is int value)
{
Console.WriteLine(value);
}
6-5. privateメソッドを呼び出す場合の注意点
リフレクションを使うと、privateメソッドを呼び出すこともできます。
C#public class Sample
{
private void Secret()
{
Console.WriteLine("privateメソッドです");
}
}
privateメソッドを取得するには、BindingFlagsを指定します。
C#using System;
using System.Reflection;
Sample sample = new Sample();
MethodInfo? method = typeof(Sample).GetMethod(
"Secret",
BindingFlags.Instance | BindingFlags.NonPublic
);
method?.Invoke(sample, null);
ただし、privateメソッドを外部から呼び出すことは、設計上あまり望ましくない場合があります。
privateメソッドは本来、そのクラスの内部だけで使うためのものです。テストやフレームワークなど特殊な目的で使うことはありますが、通常のアプリケーション開発では慎重に扱うべきです。
6-6. 通常の呼び出しより遅くなりやすい理由
リフレクションによるMethodInfo.Invokeは、通常のメソッド呼び出しより遅くなりやすいです。
通常の呼び出しは、コンパイル時に呼び出すメソッドが決まっています。
C#calculator.Add(3, 5);
一方、リフレクションでは、実行時にメソッド情報を探し、引数を確認し、動的に呼び出します。
C#method.Invoke(calculator, new object[] { 3, 5 });
このような処理が追加されるため、繰り返し大量に実行する処理ではパフォーマンスに影響することがあります。
特に、ループ内で何度もGetMethodやInvokeを呼び出すのは避けた方がよい場合があります。
C#for (int i = 0; i < 100000; i++)
{
typeof(Calculator).GetMethod("Add")?.Invoke(calculator, new object[] { 1, 2 });
}
このような場合は、取得したMethodInfoをキャッシュする、通常のメソッド呼び出しに変更する、デリゲート化するなどの工夫を検討しましょう。
7. Invokeでよくあるエラーと対処法
7-1. NullReferenceExceptionが発生する原因と対処
デリゲートやイベントに対してInvokeするとき、対象がnullだとNullReferenceExceptionが発生します。
C#Action? action = null;
action.Invoke();
このコードでは、actionがnullなのでエラーになります。
対処法は、nullチェックを行うことです。
C#if (action != null)
{
action.Invoke();
}
より簡潔には、?.Invoke()を使います。
C#action?.Invoke();
イベントの場合も同じです。
C#Completed?.Invoke(this, EventArgs.Empty);
デリゲートやイベントをInvokeするときは、「nullの可能性があるか」を必ず意識しましょう。
7-2. InvalidOperationException「別のスレッドからアクセスできません」の対処
Windows FormsやWPFで、別スレッドからUIを直接更新すると、InvalidOperationExceptionが発生することがあります。
Windows Formsでは、次のようにControl.Invokeを使います。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新しました";
}));
WPFでは、次のようにDispatcher.Invokeを使います。
C#Dispatcher.Invoke(() =>
{
textBlock.Text = "更新しました";
});
このエラーが出た場合は、「UIを別スレッドから直接操作していないか」を確認しましょう。
バックグラウンド処理からUIを更新する場合は、必ずUIスレッドに処理を渡す必要があります。
7-3. TargetParameterCountExceptionの原因と対処
TargetParameterCountExceptionは、リフレクションのMethodInfo.Invokeで引数の数が合っていない場合に発生することがあります。
たとえば、次のようなメソッドがあるとします。
C#public void Print(string message)
{
Console.WriteLine(message);
}
このメソッドは引数を1つ必要とします。
しかし、次のように引数なしで呼び出すとエラーになります。
C#method.Invoke(sample, null);
正しくは、引数を配列で渡します。
C#method.Invoke(sample, new object[] { "こんにちは" });
対処法は、呼び出すメソッドの引数の数と、Invokeに渡す配列の要素数を一致させることです。
また、引数の型も正しいか確認しましょう。
7-4. TargetInvocationExceptionの原因と対処
TargetInvocationExceptionは、リフレクションで呼び出したメソッドの内部で例外が発生した場合に、その例外が包まれて投げられることがあります。
たとえば、次のようなメソッドがあるとします。
C#public void ThrowError()
{
throw new InvalidOperationException("内部でエラーが発生しました");
}
このメソッドをMethodInfo.Invokeで呼び出すと、直接InvalidOperationExceptionが出るのではなく、TargetInvocationExceptionとして受け取ることがあります。
C#try
{
method.Invoke(sample, null);
}
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException?.Message);
}
重要なのは、実際の原因はInnerExceptionに入っていることが多い点です。
C#ex.InnerException
TargetInvocationExceptionが出たら、まずInnerExceptionを確認しましょう。
7-5. ObjectDisposedExceptionが発生するケース
Windows FormsやWPFでInvokeを使うとき、すでに破棄されたフォームやコントロールに対してInvokeすると、ObjectDisposedExceptionが発生することがあります。
たとえば、バックグラウンド処理の完了前にフォームを閉じた場合です。
C#Task.Run(() =>
{
Thread.Sleep(3000);
this.Invoke((Action)(() =>
{
label1.Text = "完了";
}));
});
この3秒の間にフォームが閉じられると、thisやlabel1が破棄されている可能性があります。
対処法としては、Invoke前に状態を確認します。
C#if (!this.IsDisposed && this.IsHandleCreated)
{
this.Invoke((Action)(() =>
{
if (!label1.IsDisposed)
{
label1.Text = "完了";
}
}));
}
また、フォームが閉じられたらバックグラウンド処理をキャンセルする設計にすることも重要です。
7-6. デッドロックを避けるための注意点
Invokeは同期的に実行されます。つまり、呼び出し元のスレッドは、UIスレッドで処理が終わるまで待機します。
この性質により、使い方を誤るとデッドロックが発生することがあります。
たとえば、UIスレッドがバックグラウンド処理の完了を待っていて、バックグラウンド処理がInvokeでUIスレッドの処理完了を待つと、互いに待ち続ける状態になる可能性があります。
このような問題を避けるには、次の点に注意します。
UIスレッドを長時間ブロックしない。
バックグラウンド処理からUI更新だけを依頼する場合はBeginInvokeやInvokeAsyncを検討する。
Task.Wait()や.ResultをUIスレッドで安易に使わない。
非同期処理では、できるだけasync/awaitを使う。
特にWPFでは、Dispatcher.InvokeよりもDispatcher.InvokeAsyncを使った方が扱いやすい場面があります。
8. Invoke・BeginInvoke・InvokeAsyncの違い
8-1. Invokeは同期的に実行する
Invokeは、指定した処理を同期的に実行します。
同期的とは、処理が終わるまで呼び出し元が待つという意味です。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
このコードでは、ラベルの更新処理が完了するまで、呼び出し元のスレッドは待機します。
処理の完了を待ちたい場合には便利ですが、UIスレッドをブロックしないよう注意が必要です。
8-2. BeginInvokeは非同期的に実行する
BeginInvokeは、指定した処理を非同期的に実行します。
C#this.BeginInvoke((Action)(() =>
{
label1.Text = "更新";
}));
この場合、処理をUIスレッドに依頼したあと、呼び出し元はすぐに次の処理へ進みます。
画面更新を依頼するだけで、結果を待つ必要がない場合に便利です。
ただし、処理がいつ完了したかを扱いたい場合には、BeginInvokeよりもInvokeAsyncの方が使いやすいことがあります。
8-3. InvokeAsyncはTaskベースで扱いやすい
InvokeAsyncは、非同期処理をTaskとして扱える仕組みです。
WPFでは、次のように書けます。
C#await Dispatcher.InvokeAsync(() =>
{
textBlock.Text = "更新しました";
});
awaitできるため、非同期処理の流れの中で自然に使えます。
たとえば、バックグラウンド処理の途中でUI更新を待ちたい場合に便利です。
C#await Task.Run(() =>
{
Thread.Sleep(1000);
});
await Dispatcher.InvokeAsync(() =>
{
textBlock.Text = "完了しました";
});
async/awaitを使っている場合は、InvokeAsyncを検討するとコードが読みやすくなることがあります。
8-4. どれを使うべきか判断する基準
Invoke、BeginInvoke、InvokeAsyncの使い分けは、処理の完了を待つ必要があるかどうかで考えるとわかりやすいです。
処理の完了を待ちたい場合はInvoke。
処理を依頼してすぐ次へ進みたい場合はBeginInvoke。
async/awaitと組み合わせたい場合はInvokeAsync。
ただし、UIスレッドを長時間止める処理をInvokeの中に書くのは避けましょう。
悪い例は次のようなコードです。
C#this.Invoke((Action)(() =>
{
Thread.Sleep(5000);
label1.Text = "完了";
}));
このコードでは、UIスレッドが5秒間止まってしまいます。
重い処理はバックグラウンドで行い、UIスレッドでは画面更新だけを行うのが基本です。
8-5. 初心者におすすめの使い分け
初心者の方は、次のように覚えるとよいでしょう。
デリゲートを実行する場合は、Invoke()または省略形を使う。
C#action.Invoke();
action();
イベントを発火する場合は、?.Invoke()を使う。
C#Completed?.Invoke(this, EventArgs.Empty);
Windows Formsで別スレッドからUI更新する場合は、Control.InvokeまたはBeginInvokeを使う。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
WPFで別スレッドからUI更新する場合は、Dispatcher.InvokeまたはDispatcher.InvokeAsyncを使う。
C#await Dispatcher.InvokeAsync(() =>
{
textBlock.Text = "更新";
});
リフレクションで動的にメソッドを呼び出す場合は、MethodInfo.Invokeを使う。
C#method.Invoke(instance, parameters);
最初はすべてを完璧に覚える必要はありません。「何に対してInvokeしているのか」を意識すると、少しずつ整理できます。
9. C#のInvokeを使うときの注意点
9-1. 必要がない場面でInvokeを使わない
Invokeは便利ですが、必要がない場面で無理に使う必要はありません。
たとえば、通常のメソッド呼び出しで十分な場合は、普通に呼び出した方が読みやすいです。
C#SayHello();
これをわざわざ次のように書く必要はありません。
C#Action action = SayHello;
action.Invoke();
もちろん、処理を変数として渡したい場合や、イベントとして扱いたい場合にはデリゲートが有効です。
しかし、単純にメソッドを実行するだけなら、通常の呼び出しで十分です。
9-2. UIスレッドを長時間ブロックしない
Windows FormsやWPFでInvokeを使うときは、UIスレッドを長時間ブロックしないように注意しましょう。
UIスレッドで重い処理を行うと、画面が固まったように見えます。
悪い例です。
C#Dispatcher.Invoke(() =>
{
Thread.Sleep(5000);
textBlock.Text = "完了";
});
このコードでは、UIスレッドが5秒間止まります。
良い例は、重い処理をバックグラウンドで行い、UI更新だけをUIスレッドで行う書き方です。
C#await Task.Run(() =>
{
Thread.Sleep(5000);
});
textBlock.Text = "完了";
または、必要に応じて次のようにします。
C#await Dispatcher.InvokeAsync(() =>
{
textBlock.Text = "完了";
});
Invokeの中には、できるだけ短いUI更新処理だけを書くようにしましょう。
9-3. 例外処理を忘れない
Invokeで呼び出した処理の中で例外が発生する可能性があります。
特にリフレクションのMethodInfo.Invokeでは、呼び出し先のメソッド内で発生した例外がTargetInvocationExceptionに包まれることがあります。
C#try
{
method.Invoke(instance, parameters);
}
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException?.Message);
}
また、UI操作ではフォームやコントロールが破棄済みの場合にObjectDisposedExceptionが発生することもあります。
C#if (!this.IsDisposed)
{
this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
}
Invokeを使う場面では、null、スレッド、引数、破棄済みオブジェクトなど、エラーの原因になりやすいポイントを意識することが大切です。
9-4. 可読性を下げない書き方を意識する
Invokeを使うコードは、ラムダ式やキャストが増えるため、読みにくくなることがあります。
たとえば、Windows Formsでは次のようなコードになりがちです。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
短い処理であれば問題ありませんが、長い処理をそのまま書くと可読性が下がります。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
button1.Enabled = true;
progressBar1.Value = 100;
textBox1.Text = "完了しました";
}));
このような場合は、UI更新処理をメソッドに分けると読みやすくなります。
C#private void UpdateUi()
{
label1.Text = "更新";
button1.Enabled = true;
progressBar1.Value = 100;
textBox1.Text = "完了しました";
}
呼び出し側は次のように書けます。
C#this.Invoke((Action)UpdateUi);
可読性を保つためには、Invokeの中に複雑な処理を書きすぎないことが大切です。
9-5. パフォーマンスが重要な処理では慎重に使う
Invokeは便利ですが、使い方によってはパフォーマンスに影響することがあります。
特にリフレクションのMethodInfo.Invokeは、通常のメソッド呼び出しよりコストが高くなりやすいです。
C#method.Invoke(instance, parameters);
少数回の呼び出しであれば問題にならないことも多いですが、大量のループ内で何度も呼び出す場合は注意が必要です。
また、UI更新のInvokeも、頻繁に呼び出しすぎると画面描画や応答性に影響することがあります。
悪い例です。
C#for (int i = 0; i < 10000; i++)
{
this.Invoke((Action)(() =>
{
label1.Text = i.ToString();
}));
}
このようなコードでは、UIスレッドに大量の更新処理が送られます。
必要に応じて、更新頻度を下げる、まとめて更新する、進捗表示だけにするなどの工夫をしましょう。
10. C#のInvokeに関するよくある質問
10-1. Invokeは必ず使わないといけない?
Invokeは必ず使わなければならないものではありません。
通常のメソッド呼び出しで十分な場合は、次のように直接呼び出せば問題ありません。
C#DoWork();
Invokeが必要になるのは、主に次のような場面です。
デリゲートに登録された処理を実行したいとき。
イベントを発火したいとき。
別スレッドからUIを更新したいとき。
リフレクションでメソッドを動的に呼び出したいとき。
つまり、Invokeは「特定の仕組みを通して処理を呼び出す必要があるとき」に使うものです。
10-2. Invokeと普通のメソッド呼び出しは何が違う?
普通のメソッド呼び出しは、メソッド名を直接書いて実行します。
C#SayHello();
Invokeは、デリゲートやオブジェクトを通じて間接的に処理を呼び出します。
C#Action action = SayHello;
action.Invoke();
また、UIスレッドへ処理を渡すためのInvokeもあります。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
リフレクションでは、メソッド情報を使って動的に呼び出します。
C#method.Invoke(instance, parameters);
同じInvokeでも、使われる場所によって役割が違います。違いを理解するには、「何に対してInvokeしているのか」を確認することが大切です。
10-3. ?.Invoke()はどんなときに使う?
?.Invoke()は、デリゲートやイベントがnullでない場合だけ実行したいときに使います。
C#Action? action = null;
action?.Invoke();
このコードは、actionがnullでもエラーになりません。
イベントでは特によく使われます。
C#public event EventHandler? Completed;
private void OnCompleted()
{
Completed?.Invoke(this, EventArgs.Empty);
}
イベントに誰も登録していない場合、イベントはnullです。その状態で通常のInvokeを行うとエラーになる可能性があります。
そのため、イベント発火では?.Invoke()がよく使われます。
10-4. WinFormsとWPFでInvokeの書き方は違う?
WinFormsとWPFでは、Invokeの書き方が異なります。
Windows Formsでは、フォームやコントロールのInvokeを使います。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
WPFでは、Dispatcher.Invokeを使います。
C#Dispatcher.Invoke(() =>
{
textBlock.Text = "更新";
});
目的はどちらも同じで、「UIスレッド上で画面更新を行うこと」です。
ただし、使うクラスや書き方が異なるため、WinFormsならControl.Invoke、WPFならDispatcher.Invokeと覚えておくとよいでしょう。
10-5. Invokeを使ってもエラーが消えないときはどうすればいい?
Invokeを使ってもエラーが消えない場合は、まずエラーの種類を確認しましょう。
NullReferenceExceptionなら、Invoke対象がnullの可能性があります。
C#action?.Invoke();
InvalidOperationExceptionなら、UIスレッド以外からUIを直接操作している可能性があります。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
TargetParameterCountExceptionなら、リフレクションで渡している引数の数が合っていない可能性があります。
C#method.Invoke(instance, new object[] { arg1, arg2 });
TargetInvocationExceptionなら、呼び出したメソッドの内部で例外が発生している可能性があります。
C#catch (TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException?.Message);
}
ObjectDisposedExceptionなら、フォームやコントロールがすでに破棄されている可能性があります。
C#if (!this.IsDisposed)
{
this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
}
Invokeを使ってもエラーが消えない場合は、「Invokeの書き方」だけでなく、「どのスレッドで実行されているか」「対象オブジェクトはnullではないか」「すでに破棄されていないか」「引数は正しいか」を確認しましょう。
まとめ
C#のInvokeは、「処理を呼び出して実行する」ための仕組みです。
ただし、Invokeには複数の種類があります。
デリゲートのInvokeは、登録されたメソッドを実行するために使います。
C#action.Invoke();
イベントでは、nullチェックを兼ねた?.Invoke()がよく使われます。
C#Completed?.Invoke(this, EventArgs.Empty);
Windows FormsのControl.Invokeは、別スレッドからUIスレッドへ処理を渡すために使います。
C#this.Invoke((Action)(() =>
{
label1.Text = "更新";
}));
WPFのDispatcher.Invokeも、UIスレッド上で画面を更新するために使います。
C#Dispatcher.Invoke(() =>
{
textBlock.Text = "更新";
});
リフレクションのMethodInfo.Invokeは、実行時に取得したメソッド情報を使って動的にメソッドを呼び出すために使います。
C#method.Invoke(instance, parameters);
初心者がC#のInvokeを理解するためには、「Invokeという名前」だけを見るのではなく、「何に対してInvokeしているのか」を確認することが重要です。
デリゲートなのか、UIコントロールなのか、Dispatcherなのか、MethodInfoなのかによって、意味も使い方も変わります。
また、Invokeを使うときは、nullチェック、UIスレッド、例外処理、パフォーマンス、デッドロックに注意が必要です。
まずは、次の3つを押さえておきましょう。
デリゲートやイベントでは?.Invoke()を使うと安全。
WinFormsやWPFでは、別スレッドからUIを直接更新せずInvokeを使う。
リフレクションのInvokeでは、引数の数や型、例外の中身に注意する。
C#のInvokeは最初は難しく感じるかもしれませんが、使われる場面ごとに整理すれば理解しやすくなります。実際のコードを読みながら、「このInvokeは何を呼び出しているのか」を意識して学習していきましょう。

