イベントの使用 (C# プログラミング ガイド) 

イベントは、オブジェクトに関連して何か意味のあることが発生したときに、そのオブジェクトのユーザーに通知するためにクラスや構造体で使用します。この通知をイベントの発生と言います。イベントを発生させるオブジェクトをイベント ソースまたはイベント送信元と言います。オブジェクトは、オブジェクト データの変更、長時間のプロセスの完了、サービスの中断など、さまざまな理由でイベントを発生させることができます。たとえば、ネットワーク リソースを使用しているオブジェクトは、ネットワーク接続が失われた場合にイベントを発生させることができます。ユーザー インターフェイス要素を表すオブジェクトは、通常、ボタンのクリックやメニューの選択などのユーザーの操作に呼応してイベントを発生させます。

イベントには、メソッドと同様に、名前とパラメータ リストを含むシグネチャがあります。このシグネチャは、次のようにデリゲート型で定義します。

public delegate void TestEventDelegate(object sender, System.EventArgs e);

多くの場合、.NET Framework のイベントのシグネチャでは、最初のパラメータがイベント ソースを参照するオブジェクトとなり、2 番目のパラメータがイベントに関連するデータを保持するクラスとなります。ただし、このようなデザインは、C# 言語によって要求も強制もされません。イベントのシグネチャは、void を返す限りは、有効なデリゲート シグネチャと同じにできます。

イベントをクラスに追加するには、event キーワードを使用し、イベントのデリゲート型と名前を指定する必要があります。次に例を示します。

public class EventSource
{
    public event TestEventDelegate TestEvent;
    private void RaiseTestEvent() { /* ... */ }
}

イベントは、publicprivateprotectedinternal、または protected internal とマークできます。これらのアクセス修飾子により、クラスのユーザーがイベントにどのようにアクセスできるかが定義されます。詳細については、「アクセス修飾子 (C# プログラミング ガイド)」を参照してください。

イベントは、static キーワードを使用して静的イベントと宣言することもできます。このように宣言すると、クラスのインスタンスが存在しない場合でも、呼び出し元がいつでもイベントを使用できるようになります。詳細については、「静的クラスと静的クラス メンバ (C# プログラミング ガイド)」を参照してください。

また、イベントは、virtual キーワードを使用して仮想イベントとマークできます。この場合、派生クラスでは、override キーワードを使用して、イベントの動作をオーバーライドできます。詳細については、「継承 (C# プログラミング ガイド)」を参照してください。仮想イベントをオーバーライドするイベントは、シールすることもできます。この場合、派生クラスでは、イベントが仮想でなくなります。最後に、イベントは抽象と宣言できます。この場合、クラスには実装が存在しないので、派生クラスで独自の実装を記述する必要があります。詳細については、「抽象クラスとシール クラス、およびクラス メンバ (C# プログラミング ガイド)」を参照してください。

イベントを発生させるために、クラスではデリゲートを呼び出し、イベントに関連するパラメータを渡すことができます。その後、デリゲートは、イベントに追加されているすべてのハンドラを呼び出します。イベントのハンドラが存在しない場合、イベントは null になるので、イベント ソースでは、イベントを発生させる前に、イベントが null でないことを確認し、NullReferenceException を回避する必要があります。また、null チェックからイベントの呼び出しまでの間に最後のハンドラが削除されるような競合状態を回避するために、イベント ソースでは、null チェックを実行してイベントを発生させる前にイベントのコピーを作成する必要もあります。次に例を示します。

private void RaiseTestEvent()
{
    // Safely invoke an event:
    TestEventDelegate temp = TestEvent;

    if (temp != null)
    {
        temp(this, new System.EventArgs());
    }
}

各イベントには、イベントを受け取る複数のハンドラを割り当てることができます。この場合、イベントは、各レシーバを自動的に呼び出します。イベントを発生させるには、レシーバの数とは無関係に、イベントの呼び出しを 1 回のみ行う必要があります。

イベントを受け取るクラスでは、イベントを受け取るメソッドを作成し、そのメソッドのデリゲートをクラス イベント自体に追加できます。このプロセスをイベントのサブスクライブと言います。

最初に、受け取り側のクラスには、デリゲート シグネチャなどの、イベント自体と同じシグネチャを持つメソッドが必要です。このメソッドは、イベント ハンドラと呼ばれ、イベントに呼応して適切な処理を実行できます。次に例を示します。

public class EventReceiver
{
    public void ReceiveTestEvent(object sender, System.EventArgs e)
    {
        System.Console.Write("Event received from ");
        System.Console.WriteLine(sender.ToString());
    }
}

各イベントには、複数のハンドラを割り当てることができます。複数のハンドラは、イベント ソースによって順次呼び出されます。1 つのハンドラが例外を発生させると、呼び出されなかったハンドラは、イベントを受け取ることができなくなります。このため、イベント ハンドラによってイベントがすばやく処理され、例外の発生が回避されるようにすることをお勧めします。

イベントをサブスクライブするには、レシーバで、イベント ハンドラをデリゲート先として使用して、イベントと同じ型のデリゲートを作成する必要があります。次に、加算代入演算子 (+=) を使用して、このデリゲートをソース オブジェクトのイベントに追加する必要があります。次に例を示します。

public void Subscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent += temp;
}

イベントをアンサブスクライブするには、レシーバで減算代入演算子 (-=) を使用して、イベント ハンドラへのデリゲートをソース オブジェクトのイベントから削除できます。次に例を示します。

public void UnSubscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent -= temp;
}

上の例では、イベント TestEvent は、フィールドと同じような方法で宣言されています。フィールドと同様に、イベントには、オブジェクトのユーザーが直接アクセスし、変更できます。ただし、フィールドと違って、加算代入演算子 (+=) と減算代入演算子 (-=) による変更しかできません。

イベントは、イベント アクセサを使用して宣言できます。イベント アクセサの構文はプロパティ アクセサと基本的に同じであり、イベント ハンドラをイベントに追加する場合は add キーワードとコード ブロックを使用し、イベント ハンドラをイベントから削除する場合は remove キーワードとコード ブロックを使用します。次に例を示します。

public class EventSource2
{
    private TestEventDelegate TestEventHandlers;
    public event TestEventDelegate TestEvent
    {
        add
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers += value;
            }
        }
        remove
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers -= value;
            }
        }
    }
    private void RaiseTestEvent()
    {
        // Safely invoke an event.
        TestEventDelegate temp = TestEventHandlers;

        if (temp != null)
        {
            temp(this, new System.EventArgs());
        }
    }
}

イベント アクセサを使用するには、ハンドラを格納および取得する機構を、イベントを発生させるクラスに設定する必要があります。上の例では、ハンドラをリストに追加および削除するために、プライベート デリゲート フィールド TestEventHandlers と加算代入演算子および減算代入演算子を使用しています。これは、アクセサなしで宣言したイベントを使用する場合と基本的に同じです。イベント レシーバで加算代入演算子 (+=) を使用してハンドラをイベントに追加すると、add アクセサが呼び出され、新しいハンドラがアクセサ内でローカル変数名の値として使用できるようになります。減算代入演算子 (-=) を使用すると、remove アクセサが呼び出され、削除されるハンドラがローカル変数名の値として使用できるようになります。これら 2 つのアクセサは void を返すため、return ステートメントは値を返すことができません。

イベントのサブスクライブとアンサブスクライブでは、イベント アクセサがクラスで宣言されているかどうかにかかわらず、同じ構文を使用します。

メモメモ

上の例では、lock ステートメントを使用して、イベント リストが複数のスレッドによって同時に操作されないようにしています。詳細については、「スレッドの同期 (C# プログラミング ガイド)」および「スレッド処理 (C# プログラミング ガイド)」を参照してください。

イベントでアクセサを使用すると、クラスが選択した方法でイベント ハンドラを格納できます。上の例ではデリゲートを使用していますが、これ以外の機構も使用できます。例については、「方法 : ディクショナリを使用してイベント インスタンスを格納する (C# プログラミング ガイド)」を参照してください。

アクセサを宣言していないイベントには、C# コンパイラによって自動的にスレッド セーフなアクセサが提供されます。抽象イベントでは、アクセサを宣言できません。また、静的アクセサでは this キーワードを使用できません。

コミュニティの追加

表示: