Windows ランタイム コンポーネントのカスタム イベントおよびイベント アクセサー

.NET Framework が Windows ランタイム をサポートすることにより、Windows ランタイム イベント パターンと .NET Framework イベント パターンの違いを非表示にして、Windows ランタイム コンポーネントのイベントを宣言するのが簡単になります。ただし、Windows ランタイム コンポーネントのカスタム イベント アクセサーを宣言する場合、Windows ランタイム パターンに従う必要があります。

Windows ランタイム のイベントを処理するために登録すると、add アクセサーはトークンを返します。登録を解除するには、remove アクセサーにこのトークンを渡します。これは Windows ランタイム イベントの add と remove アクセサーが、これまで使用してきたアクセサーとは異なるシグニチャを持つことを意味します。

さいわい、Visual Basic と C# コンパイラはこのプロセスを簡略化します。Windows ランタイム コンポーネントのカスタム アクセサーでイベントを宣言すると、コンパイラは自動的に Windows ランタイム パターンを使用します。たとえば、add アクセサーがトークンを返さない場合、コンパイラ エラーが発生します。.NET Framework では実装をサポートするための 2 種類の型を提供します。

  • EventRegistrationToken 構造体はトークンを表します。

  • EventRegistrationTokenTable<T> クラスはトークンを作成し、トークンとイベント ハンドラーの間のマップを保持します。ジェネリック型引数は、イベント引数の型です。イベント ハンドラーがそのイベントに対して最初に登録されたときに、イベントごとにこのクラスのインスタンスを作成します。

NumberChanged イベントの次のコードは、Windows ランタイム イベントの基本パターンを示しています。この例では、イベント引数オブジェクトのコンストラクターである NumberChangedEventArgs は、変更された数値を表す単一の整数パラメーターを受け取ります。

注意

これは、Windows ランタイム コンポーネントで宣言する通常のイベントでコンパイラが使用するのと同じパターンです。

    private EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>> 
        m_NumberChangedTokenTable = null;

    public event EventHandler<NumberChangedEventArgs> NumberChanged
    {
        add
        {
            return EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
                .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
                .AddEventHandler(value);
        }
        remove
        {
            EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
                .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
                .RemoveEventHandler(value);
        }
    }

    internal void OnNumberChanged(int newValue)
    {
        EventHandler<NumberChangedEventArgs> temp = 
            EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
            .GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
            .InvocationList;
        if (temp != null)
        {
            temp(this, new NumberChangedEventArgs(newValue));
        }
    }
Private m_NumberChangedTokenTable As  _
    EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs))

Public Custom Event NumberChanged As EventHandler(Of NumberChangedEventArgs)

    AddHandler(ByVal handler As EventHandler(Of NumberChangedEventArgs))
        Return EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            AddEventHandler(handler)
    End AddHandler

    RemoveHandler(ByVal token As EventRegistrationToken)
        EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            RemoveEventHandler(token)
    End RemoveHandler

    RaiseEvent(ByVal sender As Class1, ByVal args As NumberChangedEventArgs)
        Dim temp As EventHandler(Of NumberChangedEventArgs) = _
            EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
            GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
            InvocationList
        If temp IsNot Nothing Then
            temp(sender, args)
        End If
    End RaiseEvent
End Event

static (Visual Basic の Shared) GetOrCreateEventRegistrationTokenTable メソッドは、イベントの EventRegistrationTokenTable<T> オブジェクトのインスタンスを限定的に作成します。このメソッドでトークン テーブルのインスタンスを保持するクラス レベル フィールドを渡します。フィールドが空の場合、メソッドはテーブルを作成し、フィールドにテーブルへの参照を格納し、テーブルへの参照を返します。フィールドにトークン テーブル参照が既に含まれている場合、このメソッドはその参照を返します。

重要

スレッド セーフを確保するには、イベントの EventRegistrationTokenTable<T> のインスタンスを保持するフィールドは、クラス レベル フィールドである必要があります。クラス レベルのフィールドの場合、GetOrCreateEventRegistrationTokenTable メソッドでは、複数のスレッドがトークン テーブルの作成を試みるときに、すべてのスレッドでテーブルの同じインスタンスが取得されることを確認します。特定のイベントの場合、GetOrCreateEventRegistrationTokenTable メソッドへのすべての呼び出しは、同じクラス レベルのフィールドを使用する必要があります。

remove アクセサーと RaiseEvent メソッド (C# の OnRaiseEvent メソッド) で GetOrCreateEventRegistrationTokenTable メソッドを呼び出すことによって、イベント ハンドラー デリゲートが追加される前にこれらのメソッドを呼び出した場合に例外が発生しないことを確認します。

Windows ランタイム イベント パターンで使用される EventRegistrationTokenTable<T> クラスの他のメンバーには次が含まれます。

  • AddEventHandler メソッドは、イベント ハンドラー デリゲートのトークンを生成し、テーブルにデリゲートを保存し、呼び出しリストに追加し、トークンを返します。

  • RemoveEventHandler(EventRegistrationToken) メソッド オーバーロードはテーブルと呼び出しリストからデリゲートを削除します。

    注意

    AddEventHandlerRemoveEventHandler(EventRegistrationToken) メソッドはスレッド セーフを保証するためにテーブルをロックします。

  • InvocationList プロパティは、イベントを処理するために現在登録されているすべてのイベント ハンドラーを含むデリゲートを返します。このデリゲートを使用してイベントを発生させるか、Delegate クラスのメソッドを使用してハンドラーを個別に呼び出します。

    注意

    この記事の前の部分で提供した例のパターンに従い、デリゲートを呼び出す前に一時変数にコピーすることをお勧めします。これにより、スレッドが最後のハンドラーを削除して、別のスレッドがデリゲートを呼び出す直前にデリゲートが null となる競合状態を回避できます。デリゲートは変更不可であるため、コピーは引き続き有効です。

必要に応じて、アクセサーに独自のコードを配置します。スレッド セーフが問題の場合、コードに独自のロックを提供する必要があります。

C# ユーザー: Windows ランタイム イベント パターンでカスタム イベント アクセサーを作成すると、コンパイラは通常の構文のショートカットを提供しません。コードでイベント名を使用するとエラーが発生します。

Visual Basic ユーザー: .NET Framework では、イベントはすべての登録されたイベント ハンドラーを表すマルチキャスト デリゲートにすぎません。イベントを発生させることは、デリゲートを呼び出すことを意味します。Visual Basic の構文は一般に、デリゲートとの対話を非表示にします。また、スレッド セーフに関するメモに説明されているように、コンパイラはデリゲートを呼び出す前にそれをコピーします。Windows ランタイム コンポーネントでカスタム イベントを作成する場合、デリゲートと直接対処する必要があります。これは、たとえばハンドラーを個別に呼び出す場合、MulticastDelegate.GetInvocationList メソッドを使用して、イベント ハンドラーごとに個別のデリゲートが含まれる配列を取得できることも意味します。

参照

処理手順

方法 : カスタム イベント アクセサーを実装する (C# プログラミング ガイド)

関連項目

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

その他の技術情報

イベント (Visual Basic)

Windows ストア アプリおよび Windows ランタイムのための .NET Framework サポート