C++/CLI のイベントを使う

C++/CLIイベントについて調べたのでメモ。
イベントっていうかデリゲートの使い方についてのメモだな。イベント特有のアクセッサメソッド(add_xx, remove_xx, raise_xx.. )は以下では使ってないから。(コード中のeventキーワードはなくてもいい話)以下、「イベント」は、「デリゲート」に読み替えてもらって構いません。では、イベントとデリゲートの違いは何か?それは本稿の最後に。
考え方:

  • イベントは、特定のインスタンスから複数の指定インスタンスへの通知を行うためのメンバである。
  • 頭に event キーワードをつけてデリゲート型メンバ変数を宣言すれば、それがイベントとなる。
  • 通知を受信したい側は、イベントに対して自分の受け側メソッドを登録(+=)する。

イベントにするデリゲートの型は、必ず、
f( Object^ sender, PossiblyInherited_EventArgs^ args )
にしなくてはならないのかと思ったら別にそうではなく、任意delegate型を、event 宣言して、イベントハンドラとして使えるようだ。つまり、EventArgs を継承する必要すらないということだ。ってHogensonの本にも書いてある。(でないとあまりにも不便じゃね?)
以上を踏まえて、最小限の実験コードを書いてみた。
いつものように、「空のCLRアプリケーション」プロジェクトを作成し、プロジェクトの共通プロパティ>Frameworkと参照にて、System、及び、System::Windows::Formsコンポーネントを追加し、以下のコードをmain.cpp としてビルドする。

#include <iostream>

using namespace System;
using namespace System::Text;
using namespace System::Text::RegularExpressions;
using namespace System::Windows::Forms;

public ref class RxTx
{
public: 
 RxTx( String^ str ){
  m_string = str;
 }
 void Tx( const String^ str, void* data ){
  RxTxEvent( str, data );// イベントの発火
 }
 void Rx( const String^ str, void* data ){
  System::Console::WriteLine(str+", Im "+m_string+".");
 }
public:
 // RxTxHandlerデリゲート型の定義
 delegate void RxTxHandler( const String^, void* );
 // RxTxHandlerデリゲートをイベント型として宣言
 event RxTxHandler^ RxTxEvent;
 String^ m_string;
};

int main( array< String^ > ^args)
{
 RxTx^ a = gcnew RxTx("A");
 RxTx^ b = gcnew RxTx("B");
 RxTx^ c = gcnew RxTx("C");
 // ★イベントインスタンスへの、イベントハンドラの登録
 a->RxTxEvent += gcnew RxTx::RxTxHandler( a, &RxTx::Rx );
 a->RxTxEvent += gcnew RxTx::RxTxHandler( b, &RxTx::Rx );
 a->RxTxEvent += gcnew RxTx::RxTxHandler( c, &RxTx::Rx );
 a->Tx( "Hi", (void*)99 );// イベントの発火
 // 登録の削除の実験
 a->RxTxEvent -= gcnew RxTx::RxTxHandler( b, &RxTx::Rx );
 a->Tx( "Hi", (void*)99 );// イベントの発火
 return 0;
}

ここで、デリゲートというものが、任意戻り値、任意引数リストのインタフェースの定義を行う役目を果たしている。それをイベントと定義するのが、eventキーワードだ。
イベントインスタンスへの、イベントハンドラ登録の際に、gcnewを使ってイベントハンドラを登録する場面(★)があるが、これは単に、「イベントハンドラであるデリゲート型へのコールバックメソッドの追加」という意味であり、その引数は必ず、
( イベントを受け取るインスタンス, イベントを受け取るメソッド名)
となっている。第2引数のメソッドは、デリゲート型の引数定義と同じになっていないと、コンパイルエラーとなる。これがわからない人はデリゲートというものがわかってないだけので、ついでにデリゲートの勉強にもなったじゃんか今回、な。
だからイベントというよりは、これらは全て、デリゲートというものの使い方なのであった。ちゃんちゃん。
では、イベントとデリゲートの違いは何なのか?その答えは「イベントとして定義したデリゲートインスタンスは、それを定義したクラス内部でしか、Invokeできない、これに対してイベントではないただのデリゲートは、それが外部から参照可能な場合、外部からInvokeすることも出来てしまう、ありゃ、そりゃ困りそうだ。」ということなのだ、という噂がある。