デリゲート (C++/CX)
delegate
キーワードは、Windows ランタイムで標準 C++ の関数オブジェクトに相当する参照型を宣言するために使用されます。 関数シグネチャに似たデリゲート宣言。これは、ラップされた関数が持つ必要のある、戻り値の型およびパラメーターの型を指定します。 これは、ユーザー定義のデリゲート宣言です。
public delegate void PrimeFoundHandler(int result);
多くの場合、デリゲートはイベントと一緒に使用されます。 イベントは、デリゲート型を持ちます。これは、クラスがインターフェイスの型を持つことができるのとよく似た概念です。 デリゲートは、イベント ハンドラーが適切に満たすコントラクトを表します。 以前に定義されたデリゲートの型を持つイベント クラス メンバーを次に示します。
event PrimeFoundHandler^ primeFoundEvent;
Windows ランタイム アプリケーションのバイナリ インターフェイスの境界を超えてクライアントに公開されるデリゲートを宣言する場合は、Windows::Foundation::TypedEventHandler<TSender, TResult> を使用します。 このデリゲートには、デリゲートが Javascript クライアントによって利用できるようにする、事前定義されたプロキシとスタブ バイナリがあります。
デリゲートの利用
ユニバーサル Windows プラットフォーム アプリを作成する場合、Windows ランタイム クラスが公開するイベントの型としてデリゲートを使うことがよくあります。 イベントにサブスクライブするには、デリゲートの署名に一致する関数 (ラムダ) を指定することで、そのデリゲート型のインスタンスを作成します。 そして、 +=
演算子を使用して、デリゲート オブジェクトをクラスのイベント メンバーに渡します。 これは、イベントのサブスクライブと呼ばれます。 クラスのインスタンスがイベントを "発生" させると、ユーザーのオブジェクトやその他のオブジェクトによって追加された他のハンドラーと共に、関数が呼び出されます。
ヒント
Visual Studio は、イベント ハンドラーを作成するときに便利です。 たとえば、イベント ハンドラーを XAML マークアップで指定すると、ツール ヒントが表示されます。 ツール ヒントが選択されると、Visual Studio は、自動的にイベント ハンドラー メソッドを作成し、パブリッシャー クラスのイベントに関連付けます。
次の例に、基本的なパターンを示します。 Windows::Foundation::TypedEventHandler
はデリゲート型です。 ハンドラー関数は、名前付き関数を使用して生成されます。
app.h で:
[Windows::Foundation::Metadata::WebHostHiddenAttribute]
ref class App sealed
{
void InitializeSensor();
void SensorReadingEventHandler(Windows::Devices::Sensors::LightSensor^ sender,
Windows::Devices::Sensors::LightSensorReadingChangedEventArgs^ args);
float m_oldReading;
Windows::Devices::Sensors::LightSensor^ m_sensor;
};
app.cpp で:
void App::InitializeSensor()
{
// using namespace Windows::Devices::Sensors;
// using namespace Windows::Foundation;
m_sensor = LightSensor::GetDefault();
// Create the event handler delegate and add
// it to the object's event handler list.
m_sensor->ReadingChanged += ref new TypedEventHandler<LightSensor^,
LightSensorReadingChangedEventArgs^>( this,
&App::SensorReadingEventHandler);
}
void App::SensorReadingEventHandler(LightSensor^ sender,
LightSensorReadingChangedEventArgs^ args)
{
LightSensorReading^ reading = args->Reading;
if (reading->IlluminanceInLux > m_oldReading)
{/*...*/}
}
警告
細心の注意を払って循環参照を回避する場合を除き、一般にイベント ハンドラーにはラムダではなく名前付き関数を使用する方が適しています。 名前付き関数では弱い参照によって "this" ポインターをキャプチャしますが、ラムダでは強い参照によって "this" ポインターをキャプチャし、循環参照を作成します。 詳細については、弱参照と循環の解除に関する記事を参照してください。
名前付け規則により、Windows ランタイムによって定義されたイベント ハンドラー デリゲート名は *EventHandler の形式 (RoutedEventHandler、SizeChangedEventHandler、SuspendingEventHandler など) になります。 同様に、慣例に基づき、イベント ハンドラー デリゲートは 2 つのパラメーターを持ち、void を返します。 型パラメーターがないデリゲートでは、最初のパラメーターは Platform::Object^型です。これは、イベントを発生させたオブジェクトである送信元への参照を保持します。 イベント ハンドラー メソッドで引数を使用する前に、元の型にキャスト バックする必要があります。 型パラメーターを持つイベント ハンドラー デリゲートでは、最初の型パラメーターは送信元の種類を指定し、2 番目のパラメーターはイベントに関する情報を保持する ref クラスへのハンドルです。 慣例により、そのクラスの名前は *EventArgs です。 たとえば、RoutedEventHandler デリゲートには RoutedEventArgs^ 型の 2 番目のパラメーターがあり、DragEventHander には DragEventArgs^ 型の 2 番目のパラメーターがあります。
名前付け規則により、非同期操作が完了したときに実行されるコードをラップするデリゲートには、*CompletedHandler という名前が付けられます。 これらのデリゲートは、イベントではなくクラスのプロパティとして定義されます。 したがって、サブスクライブするために +=
演算子を使用する必要はありません。デリゲート オブジェクトをプロパティに割り当てるだけです。
ヒント
C++ IntelliSense ではデリゲート署名がフルに表示されないため、EventArgs パラメーターの特定の型を判断するための役には立ちません。 型を見つけるには、 オブジェクト ブラウザーでデリゲートの Invoke
メソッドを確認します。
カスタム デリゲートの作成
独自のデリゲートを定義して、イベント ハンドラーを定義したり、コンシューマーが Windows ランタイム コンポーネントにカスタム機能を渡すことができるようにしたりできます。 Windows ランタイムの他の型と同様に、パブリック デリゲートをジェネリックとして宣言することはできません。
宣言
デリゲートの宣言は関数宣言に似ていますが、デリゲートは型である点が異なります。 クラス宣言の内部にデリゲート宣言を入れ子にすることもできますが、通常は名前空間スコープでデリゲートを宣言します。 次のデリゲートは、入力として ContactInfo^
を受け取って Platform::String^
を返す関数をカプセル化します。
public delegate Platform::String^ CustomStringDelegate(ContactInfo^ ci);
デリゲート型を宣言した後、その型のクラス メンバー、またはパラメーターとしてその型のオブジェクトを受け取るメソッドを宣言できます。 メソッドまたは関数は、デリゲート型を返すこともできます。 次の例では、 ToCustomString
メソッドは入力パラメーターとしてデリゲートを受け取ります。 このメソッドを使用すると、 ContactInfo
オブジェクトのパブリック プロパティの一部またはすべてから文字列を構築するカスタム関数を、クライアント コードで提供することができます。
public ref class ContactInfo sealed
{
public:
ContactInfo(){}
ContactInfo(Platform::String^ saluation, Platform::String^ last, Platform::String^ first, Platform::String^ address1);
property Platform::String^ Salutation;
property Platform::String^ LastName;
property Platform::String^ FirstName;
property Platform::String^ Address1;
//...other properties
Platform::String^ ToCustomString(CustomStringDelegate^ func)
{
return func(this);
}
};
Note
Windows ランタイムの参照型の場合と同様に、デリゲート型を参照するときは "^" シンボルを使用します。
イベント宣言には常にデリゲート型が含まれます。 この例は、Windows ランタイムでの一般的なデリゲート型のシグネチャを示しています。
public delegate void RoutedEventHandler(
Platform::Object^ sender,
Windows::UI::Xaml::RoutedEventArgs^ e
);
Click
クラスの Windows:: UI::Xaml::Controls::Primitives::ButtonBase
イベントは、 RoutedEventHandler
型です。 詳細については、「イベント」を参照してください。
クライアント コードでは、 ref new
を使用し、デリゲートのシグネチャと互換性のあるラムダを提供することで、まずデリゲート インスタンスを構築し、カスタム動作を定義します。
CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
return c->FirstName + " " + c->LastName;
});
次に、メンバー関数を呼び出し、デリゲートを渡します。 ci
が ContactInfo^
インスタンスであり、 textBlock
が XAML TextBlock^
であると想定します。
textBlock->Text = ci->ToCustomString( func );
次の例では、クライアント アプリが、Vector
の各項目に対してデリゲートを実行する Windows ランタイム コンポーネントのパブリック メソッドにカスタム デリゲートを渡します。
//Client app
obj = ref new DelegatesEvents::Class1();
CustomStringDelegate^ myDel = ref new CustomStringDelegate([] (ContactInfo^ c)
{
return c->Salutation + " " + c->LastName;
});
IVector<String^>^ mycontacts = obj->GetCustomContactStrings(myDel);
std::for_each(begin(mycontacts), end(mycontacts), [this] (String^ s)
{
this->ContactString->Text += s + " ";
});
// Public method in WinRT component.
IVector<String^>^ Class1::GetCustomContactStrings(CustomStringDelegate^ del)
{
namespace WFC = Windows::Foundation::Collections;
Vector<String^>^ contacts = ref new Vector<String^>();
VectorIterator<ContactInfo^> i = WFC::begin(m_contacts);
std::for_each( i ,WFC::end(m_contacts), [contacts, del](ContactInfo^ ci)
{
contacts->Append(del(ci));
});
return contacts;
}
建設
次のいずれかのオブジェクトからデリゲートを構築できます。
lambda
静的関数
メンバーへのポインター
std::function
次の例は、これらのオブジェクトからデリゲートを構築する方法を示しています。 構築に使用したオブジェクトの種類に関係なく、デリゲートの利用方法はまったく同じです。
ContactInfo^ ci = ref new ContactInfo("Mr.", "Michael", "Jurek", "1234 Compiler Way");
// Lambda. (Avoid capturing "this" or class members.)
CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
return c->Salutation + " " + c->FirstName + " " + c->LastName;
});
// Static function.
// static Platform::String^ GetFirstAndLast(ContactInfo^ info);
CustomStringDelegate^ func2 = ref new CustomStringDelegate(Class1::GetFirstAndLast);
// Pointer to member.
// Platform::String^ GetSalutationAndLast(ContactInfo^ info)
CustomStringDelegate^ func3 = ref new CustomStringDelegate(this, &DelegatesEvents::Class1::GetSalutationAndLast);
// std::function
std::function<String^ (ContactInfo^)> f = Class1::GetFirstAndLast;
CustomStringDelegate^ func4 = ref new CustomStringDelegate(f);
// Consume the delegates. Output depends on the
// implementation of the functions you provide.
textBlock->Text = func(ci);
textBlock2->Text = func2(ci);
textBlock3->Text = func3(ci);
textBlock4->Text = func4(ci);
警告
"this" ポインターをキャプチャするラムダを使用する場合は、ラムダを終了する前に -=
演算子を使用し、明示的にイベントから登録解除してください。 詳細については、「イベント」を参照してください。
汎用デリゲート
C++/CX 内の汎用デリゲートには、ジェネリック クラスの宣言に似た制限があります。 これらを public として宣言することはできません。 プライベートまたは内部の汎用デリゲートを宣言して C++ から使用することはできますが、.NET または JavaScript クライアントは.winmd メタデータに出力されないため、それを使用できません。 この例では、次のように C++ でのみ利用できる汎用デリゲートを宣言します。
generic <typename T>
delegate void MyEventHandler(T p1, T p2);
次の例では、クラス定義の内部で、デリゲートの特化されたインスタンスを宣言します。
MyEventHandler<float>^ myDelegate;
デリゲートとスレッド
関数オブジェクトと同様に、デリゲートには将来いつか実行されるコードが含まれます。 デリゲートを作成して渡すコードと、デリゲートを受け取って実行する関数が同じスレッドで実行されている場合、動作は比較的簡単です。 そのスレッドが UI スレッドの場合、デリゲートは XAML コントロールなどのユーザー インターフェイス オブジェクトを直接処理できます。
クライアント アプリが、スレッド アパートメントで実行される Windows ランタイム コンポーネントを読み込み、そのコンポーネントにデリゲートを提供する場合、既定ではデリゲートは STA スレッドで直接呼び出されます。 ほとんどの Windows ランタイム コンポーネントは、STA または MTA で実行できます。
デリゲートを実行するコードが別のスレッド (concurrency::task オブジェクトのコンテキスト内など) で実行される場合は、共有データへのアクセスを同期する必要があります。 たとえば、デリゲートにベクターへの参照が含まれ、XAML コントロールが同じベクターへの参照を持つ場合は、デリゲートと XAML コントロールが同時にベクターにアクセスしようとしたときに発生する可能性のあるデッドロックや競合状態を回避する手順を実行する必要があります。 また、デリゲートが呼び出される前にスコープ外に出る可能性がある参照ローカル変数によってデリゲートがキャプチャを試みないように注意する必要もあります。
作成したデリゲートがそれを作成したスレッドと同じスレッドでコールバックされるようにする場合 (MTA アパートメントで実行されるコンポーネントにデリゲートを渡し、それが作成者と同じスレッドで呼び出されるようにする場合など) は、2 つ目の CallbackContext
パラメーターを受け取るデリゲート コンストラクターのオーバーロードを使用します。 このオーバーロードは、登録されたプロキシ/スタブを持つデリゲートでのみ使用してください。Windows.winmd で定義されたすべてのデリゲートが登録されているわけではありません。
.NET でのイベント ハンドラーの扱いに慣れている場合は、イベントを発生させる前に、イベントのローカル コピーを作成することが推奨されている点を理解しているはずです。 この結果、イベントが呼び出される直前にイベント ハンドラーが削除される可能性がある、という競合状態を回避できます。 イベント ハンドラーが追加または削除されると、新しいハンドラー リストが作成されるため、C++/CX でこれを行う必要はありません。 C++ オブジェクトはイベントを発生させる前に、ハンドラーの一覧の参照カウントをインクリメントするため、すべてのハンドラーが有効であることが保証されます。 ただし、このことは同時に、利用側スレッドでイベント ハンドラーを削除した場合でも、発行側オブジェクトがそのリストのコピーを使用して引き続き動作している状況では (もうそのコピーは古いのですが)、依然としてそのハンドラーが呼び出される可能性があることを意味します。 発行側オブジェクトは、自らが次にそのイベントを発生させるまでは、更新されたリストを取得しません。