イベントの処理と発生
.NET でのイベントは、デリゲート モデルに基づいています。 デリゲート モデルはオブザーバー デザイン パターンに従って、サブスクライバーがプロバイダーに登録して通知を受信できるようにします。 イベントの送信元がイベント発生の通知をプッシュしたら、イベント レシーバーはその通知を受信して、通知に対する応答を定義します。 ここでは、デリゲート モデルの主要コンポーネント、アプリケーションでイベントを利用する方法、およびコードでイベントを実装する方法について説明します。
イベント
イベントは、アクションの発生を知らせるために、オブジェクトによって送信されるメッセージです。 アクションは、ユーザーがボタンのクリックなどの対話的操作を行った場合や、プロパティの値の変更など、なんらかのプログラム ロジックによって発生します。 イベントを発生させるオブジェクトを "イベントの送信元" と呼びます。 イベントの送信元は、発生させたイベントをどのオブジェクトまたはメソッドが受信する (処理する) かについての情報を持っていません。 このイベントはイベントの送信元のメンバーです。たとえば、Click イベントは Button クラスのメンバーであり、PropertyChanged イベントは INotifyPropertyChanged インターフェイスを実装するクラスのメンバーです。
イベントを定義するには、イベント クラスのシグネチャで C# の event
または Visual Basic の Event
キーワードを使用し、イベント用のデリゲートの型を指定します。 デリゲートについては、次のセクションで説明します。
通常、イベントを発生させるには、protected
や virtual
(C# の場合)、または Protected
や Overridable
(Visual Basic の場合) としてマークされたメソッドを追加します。 このメソッドには On
EventName の形式で名前を付けます (OnDataReceived
など)。 メソッドでは、イベント データ オブジェクトを指定するパラメーターを 1 つ受け取る必要があります。これは、EventArgs 型または派生型のオブジェクトです。 このメソッドを指定すると、イベントを発生させるためのロジックを派生クラスでオーバーライドできます。 派生クラスは常に基底クラスの On
EventName メソッドを呼び出して、登録されたデリゲートがイベントを必ず受信できるようにします。
次の例は、ThresholdReached
という名前のイベントを宣言する方法を示しています。 イベントは EventHandler デリゲートに関連付けられ、OnThresholdReached
という名前のメソッドで発生します。
class Counter
{
public event EventHandler ThresholdReached;
protected virtual void OnThresholdReached(EventArgs e)
{
EventHandler handler = ThresholdReached;
handler?.Invoke(this, e);
}
// provide remaining implementation for the class
}
Public Class Counter
Public Event ThresholdReached As EventHandler
Protected Overridable Sub OnThresholdReached(e As EventArgs)
RaiseEvent ThresholdReached(Me, e)
End Sub
' provide remaining implementation for the class
End Class
デリゲート
デリゲートは、メソッドへの参照を保持する型です。 デリゲートは、それが参照するメソッドの戻り値とパラメーターを示すシグネチャを使って宣言され、そのシグネチャに一致するメソッドへの参照だけを保持できます。 これにより、デリゲートはタイプ セーフな関数ポインターやコールバックと同等の機能を持つことができます。 デリゲート クラスは、宣言すると、定義は不要です。
.NET でのデリゲートの用途は多数あります。 イベントのコンテキストでは、デリゲートは、イベント ソースとイベントを処理するコードの間を仲介するもの、つまりポインター的な機構です。 デリゲートをイベントに関連付けるには、デリゲート型をイベント宣言に追加します。 デリゲートの詳細については、「Delegate クラス」を参照してください。
.NET には EventHandler および EventHandler<TEventArgs> デリゲートが用意されていて、ほとんどのイベント シナリオがサポートされます。 EventHandler デリゲートは、イベント データを含まないすべてのイベントに対して使用します。 EventHandler<TEventArgs> デリゲートは、イベントに関するデータを含むイベントに使用します。 これらのデリゲートには戻り値の型の値がなく、2 つのパラメーター (イベント ソース用オブジェクトとイベント データ用オブジェクト) を受け取ります。
デリゲートはマルチキャストです。つまり、複数のイベント処理メソッドへの参照を保持できます。 詳細については、Delegate のリファレンス ページを参照してください。 デリゲートでは、イベント処理を柔軟に、そして詳細に制御できます。 デリゲートは、イベントに対して登録されているイベント ハンドラーのリストを管理することで、そのイベントを発生させるクラスのイベント ディスパッチャーとして動作します。
EventHandler デリゲートおよび EventHandler<TEventArgs> デリゲートが動作しないシナリオについては、デリゲートを定義できます。 デリゲートの定義が必要なシナリオは非常にまれで、たとえば、ジェネリックを認識しないコードを使用する必要がある場合などです。 デリゲートは、宣言内で C# の delegate
および Visual Basic の Delegate
キーワードを使ってマークします。 次の例は、ThresholdReachedEventHandler
という名前のデリゲートを宣言する方法を示しています。
public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);
Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)
イベント データ
イベントに関連付けられたデータは、イベント データ クラスを使用して提供できます。 .NET には、ご自分のアプリケーション内で使用できるイベント データ クラスが多数用意されています。 たとえば、SerialDataReceivedEventArgs クラスは、SerialPort.DataReceived イベントのイベント データ クラスです。 .NET の名前付けパターンでは、すべてのイベント データ クラス名の末尾に EventArgs
が付きます。 イベントに関連付けられているイベント データ クラスは、そのイベントのデリゲートを見ればわかります。 たとえば、SerialDataReceivedEventHandler デリゲートには、パラメーターの 1 つとして SerialDataReceivedEventArgs クラスが含まれます。
EventArgs は、すべてのイベント データ クラスの基本型です。 また、EventArgs は、イベントにデータが関連付けられていないときに使用するクラスでもあります。 何かが発生したことを他のクラスに通知することのみが目的のイベント、つまり、データの受け渡しを必要としないイベントを作成する場合は、デリゲートの 2 番目のパラメーターとして EventArgs クラスを追加します。 データが指定されていない場合は、EventArgs.Empty 値を渡すことができます。 EventHandler デリゲートには、パラメーターとして EventArgs クラスが含まれます。
カスタマイズされたイベント データ クラスを作成する場合は、EventArgsから派生したクラスを作成し、イベントに関連するデータを渡すのに必要なメンバーを指定します。 通常は、.NET と同じ名前付けパターンを使い、イベント データ クラス名の末尾に EventArgs
を付ける必要があります。
次の例は、ThresholdReachedEventArgs
という名前のイベント データを示しています。 これには、発生したイベントに応じた特定のプロパティが含まれます。
public class ThresholdReachedEventArgs : EventArgs
{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}
Public Class ThresholdReachedEventArgs
Inherits EventArgs
Public Property Threshold As Integer
Public Property TimeReached As DateTime
End Class
イベント ハンドラー
イベントに応答するには、イベント レシーバーのイベント ハンドラー メソッドを定義します。 このメソッドは、処理するイベントのデリゲートのシグネチャと一致する必要があります。 イベント ハンドラーでは、ユーザーがボタンをクリックしたときにユーザー入力を収集するなど、イベントが発生したときに必要なアクションを実行します。 イベントが発生したときに通知を受け取るには、イベント ハンドラー メソッドがイベントをサブスクライブする必要があります。
次の例は、c_ThresholdReached
デリゲートのシグネチャと一致する、EventHandler という名前のイベント ハンドラー メソッドを示しています。 メソッドは、ThresholdReached
イベントをサブスクライブします。
class Program
{
static void Main()
{
var c = new Counter();
c.ThresholdReached += c_ThresholdReached;
// provide remaining implementation for the class
}
static void c_ThresholdReached(object sender, EventArgs e)
{
Console.WriteLine("The threshold was reached.");
}
}
Module Module1
Sub Main()
Dim c As New Counter()
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached
' provide remaining implementation for the class
End Sub
Sub c_ThresholdReached(sender As Object, e As EventArgs)
Console.WriteLine("The threshold was reached.")
End Sub
End Module
静的および動的イベント ハンドラー
.NET を使用すると、静的または動的にイベント通知のためのサブスクライバーを登録できます。 静的なイベント ハンドラーは、処理対象のイベントのクラスの有効期間にわたって有効になります。 動的なイベント ハンドラーは、プログラムの実行中、通常は条件付きのプログラム ロジックに応答する形で、明示的にアクティブ化または非アクティブ化されます。 たとえば、特定の条件下でのみイベント通知が必要な場合や、複数のイベント ハンドラーを持つアプリケーションで、実行時の条件に応じて適切なハンドラーを決定する場合などに使用できます。 前のセクションの例は、イベント ハンドラーを動的に追加する方法を示しています。 詳細については、イベント (Visual Basic の場合) とイベント (C# の場合) を参照してください。
複数のイベントの発生
クラスで複数のイベントを発生させる場合、コンパイラでは、イベント デリゲートのインスタンスごとに 1 つのフィールドが生成されます。 イベントの数が多い場合は、デリゲート 1 つあたり 1 フィールドというストレージ コストが許容されない可能性があります。 そのような状況に備えて、.NET には、イベント デリゲートを格納するために任意に選択した別のデータ構造と一緒に使用できる、イベント プロパティが用意されています。
イベント プロパティは、イベント アクセサーを伴うイベント宣言によって構成されます。 イベント アクセサーは、ストレージ データ構造におけるイベント デリゲート インスタンスの追加または削除を定義するメソッドです。 イベント プロパティを使用すると、イベント プロパティは各イベント デリゲートを呼び出す前に取得する必要があるので、イベント フィールドよりも低速です。 つまり、メモリを取るか、速度を取るかの比較検討になります。 クラスで、発生頻度の低いイベントを数多く定義する場合は、イベント プロパティを実装することをお勧めします。 詳細については、イベント プロパティを使用して複数のイベントを処理する」をご覧ください。
関連記事
Title | 説明 |
---|---|
方法: イベントを発生させる/処理する | イベントの発生例と実装例が含まれます。 |
方法: イベント プロパティを使用して複数のイベントを処理する | イベント プロパティを使用して複数のイベントを処理する方法を示します。 |
オブサーバー デザイン パターン | サブスクライバーがプロバイダーに登録して、通知を受信できるようにするデザイン パターンについて説明します。 |