27.デリゲート
デリゲート定義は System::Delegate から派生したクラスを定義します。
デリゲート・インスタンスは一つ以上のメンバ関数を
呼び出しリスト(invocation-list)にカプセル化し、その関数のそれぞれが
呼び出し可能エンティティ(callable entity)として参照されます。
インスタンス関数には、呼び出し可能エンティティはインスタンスとそのインスタンスのメンバによって成り立ちます。
静的関数には、呼び出し可能エンティティは単にあるメンバ関数のみで成り立ちます。
デリゲート・インスタンスと適切な引数の組を与えることで、デリゲート・インスタンスの関数全てをその引数のセットを与えて呼び出すことができます。
[注意:メンバ関数へのポインタと違って、デリゲート型と互換性のあるシグネイチャを持つ(
§27.1 )関数である限り、デリゲート・インスタンスは任意のクラスのメンバに結びつけることが可能です。
これはデリゲートの"匿名的な"呼び出しに適しています。]
メタデータの詳細は
§34.14 を参照のこと。
27.1 デリゲート型定義
delegate-specifier(デリゲート指定子)は新しいデリゲート型を定義する型指定子(
§12 )です。
delegate-specifier:
attributesopt top-level-type-visibilityopt delegate type-specifier-seq declarator ;
デリゲート指定子(delegate-specifier)は属性(
§29 )の組を含めることが可能です。
非ネスト型デリゲートは
上層型認識(top-level-type-visibility)(
§12.4 )の public か private を使って、そのクラスのアクセス可能性を随意に指定することができます。
type-specifier-seq(型指定子序列)と
declarator(宣言子)は共にデリゲート型を構成し、
cv-qualifier-seqや
exception-specification (例外指定子)を伴わない点を除いて、
関数宣言の形態を取るべきです。
関数宣言における関数の名前はデリゲート型の名前に当たります。
オプションとなる
parameter-declaration-clause(パラメータ宣言節)はデリゲートのパラメータを指定し、デリゲートにおいてはパラメータの省略はあるべきではない点を除いて、それは関数のパラメータに対応します。
関数宣言の返却値型はデリゲート型の返却値型です。
デリゲートそれ自身の型を除いて、デリゲート指定子中に型を定義するべきではありません。
関数とデリゲート型はもし次の双方について真であれば、
compatible(互換)です。
- それらは同じパラメータ数、同じ型を同じ順序で、同じパラメータ修飾子を持つ。
- それらの返却値型(return-type)が同じである。
デリゲート型は名前等価であり、構造等価ではありません。
特に、同一のパラメータ・リストを持ち、同一の返却値型を持つ二つの異なるデリゲート型は違うデリゲート型であると認識します。[例:
delegate int D1(int i, double d);
ref struct A {
static int M1(int a, double d) { ... }
};
ref struct B {
delegate int D2(int c, double d);
static int M2(int f, double g) { ... }
static void M3(int k, double l) { ... }
static int M4(int g) { ... }
static void M5(int g) { ... }
};
D1^ d1;
d1 = gcnew D1(&A::M1); // ok
d1 += gcnew D1(&B::M2); // ok
d1 += gcnew D1(&B::M3); // エラー、型に互換性がありません。
d1 += gcnew D1(&B::M4); // エラー、型に互換性がありません。
d1 += gcnew D1(&B::M5); // エラー、型に互換性がありません。
B::D2^ d2;
d2 = gcnew D2(&A::M1); // ok
d2 += gcnew D2(&B::M2); // ok
d2 += gcnew D2(&B::M3); // エラー、型に互換性がありません。
d2 += gcnew D2(&B::M4); // エラー、型に互換性がありません。
d2 += gcnew D2(&B::M5); // エラー、型に互換性がありません。
d1 = d2; // エラー、型が違います。
]
デリゲート型を定義するただ一つの方法は
デリゲート指定子( delegate-specifier )を通してのみです。
デリゲート型は System::Delegate から派生したクラス型です。
デリゲート型は暗黙のうちに sealed 指定されているので、デリゲート型からさらに派生することはできません。
また、非デリゲート型クラスをSystem::Delegate型から派生することもできません。
System::Delegate 型はそれ自身はデリゲート型ではありません。
それはあくまで全てのデリゲート型が派生するrefクラス型に過ぎないのです。
C++/CLI はデリゲートのインスタンス化と呼び出しのための構文を提供します。
インスタンス化を除いて、クラスやクラス・インスタンスに行うことのできる全ての操作はデリゲート・クラスやそのインスタンスにも
おのおの適用することができます。
特に、System::Delegate 型のメンバに通常のメンバアクセスの構文でアクセスすることができます。
デリゲート・インスタンスにカプセル化された関数のセットは呼び出しリストと呼ばれます。
ある一つの関数からデリゲート・インスタンスが生成(
§27.2)されると、
デリゲートはその関数をカプセル化し、その呼び出しリストはただ一つのエンティティを含むこととなります。
しかしながら、二つの nullptr でないデリゲート・インスタンスが結合されると、その呼び出しリストは連結され、左のオペランドから右のオペランドへの順に、
二つ以上のエンティティを含んだ新しい呼び出しリストが形成されます。
デリゲートは二項演算子 +(
§15.6.1 )と += 演算子(
§15.12)を使って結合します。
デリゲートは二項演算子 -(
§15.6.2 )と -= 演算子(
§15.12)によって呼び出しリストから削除することもできます。
デリゲートは等価性(
§15.8.2)によって比較することが可能です。
呼び出しリストはnullptrをカプセル化したソロ、ないし、複合エンティティを決して含みません。
nullptrデリゲートで非nullptrデリゲートなどとの結合を試みることは、返値として非nullptrデリゲートのハンドルを結果とします。
新しい呼び出しリストは作成されません。
nullptr デリゲートを非nullptrデリゲートから削除する試みは、返値として非nullptrデリゲートのハンドルを結果とします。
新しい呼び出しリストは作成されません。
一度作成されたら、呼び出しリストを変更することはできません。
二つの非nullptrデリゲートの結合と削除操作は新しい呼び出しリストの作成を結果とします。
呼び出しリストは決して空にはなれません。少なくとも一つのエントリーを持つか、さもなくば、リストが存在しません。
呼び出しリストは複数のエンティティを含むことができます。
その場合、リストの呼び出しは複数のエンティティがエンティティがある毎に一回呼び出しする結果となります。
呼び出しリストからエンティティのリストが削除された時、形成されているリストに後者のリスト中のものが見つかる最初の発生に対して、そのひとつが削除されます。
もし、そのようなリストが見あたらなかったら、検索したリストが結果となります。
[例:次の例はデリゲートのインスタンス数を示し、それに対応した呼び出しリストを示します。
delegate void D(int x);
ref struct Test {
static void M1(int i) { ... }
static void M2(int i) { ... }
};
int main() {
D^ cd1 = gcnew D(&Test::M1); // M1
D^ cd2 = gcnew D(&Test::M2); // M2
D^ cd3 = cd1 + cd2; // M1 + M2;
D^ cd4 = cd3 - cd1; // M2
}
]
27.2 デリゲート型インスタンス化
各々のデリゲート型毎に、以下のような、二つのコンストラクタを持ちます。
- 一つの引数、del-con-arg1 を持つコンストラクタは、静的関数や名前空間中の関数からデリゲートを作成します。
この del-con-arg1 は静的メンバ関数のアドレス、グローバル、ないし、名前空間中の関数のアドレスで、インスタンス化デリゲート型において互換性があります。
- 二つの引数、各々、del-con-arg2、del-con-argl3 を取るコンストラクタがあります。
これはインスタンス関数からデリゲートを作成するために使われます。
この場合、del-con-argl2 は CLI クラス・インスタンスへの参照であり、del-con-argl3 はそのインスタンスの型に直接定義されたインスタンス関数のアドレスであるべきです。
[例:
delegate void D(int x);
ref struct Test {
static void M1(int i) { ... }
void M2(int i) { ... }
};
int main() {
D^ cd1 = gcnew D(&Test::M1); // 静的関数
Test^ t = gcnew Test;
D^ cd2 = gcnew D(t, &Test::M2); // インスタンス関数
}
]
一度インスタンス化されると、デリゲート・インスタンスは常に同じターゲットとなるCLIクラス・インスタンスと関数を参照することになります。
[注意:忘れないように。二つのデリゲートが結びつけられた時、もしくは、あるデリゲートがもう一方から削除された時、新しいデリゲートはそれ自身で呼び出しリストを持つ結果となります。
デリゲートの結合、削除した呼び出しリスト自身は変わらずに残っています。]
デリゲートが関数名から作成された時、形成されたパラメータ・リストとデリゲートの返却型は、選択された多重定義関数によって決定します[例:例中の、
delegate double DoubleFunc(double x);
ref struct A {
static float Square(float x) {
return x*x;
}
static double Square(double x) {
return x*x;
}
};
int main() {
DoubleFunc^ f = gcnew DoubleFunc(&A::Square);
}
変数 f は、二番目の関数が厳密に DoubleFunc の正式なパラメータ・リストと返却型にマッチするので、 二番目の Square 関数を参照してデリゲートをインスタンス化します。
二番目の Square 関数がそこになかったら、プログラムは不正となります。]
27.3 デリゲート型の呼び出し
デリゲート void D() が与えられたとして、関数呼び出し D() は呼び出し D->Invoke の短縮形です。
デリゲートの呼び出しは、CLI標準の Invoke メンバで指定された構文を持ちます。
[注意:ここにその標準が何を要請しているかサマリーを置きます。
ひとつのエントリを呼び出しリストに持っているデリゲートインスタンスが呼び出された時、デリゲートは与えられた同じ引数を関数にあてがい呼び出し、関数に参照された同じ値を返します。
もし、そのデリゲートの呼び出し中に例外が発生し、呼び出した関数中でその例外がキャッチされなかった場合、例外 catch 節の探索は、デリゲートを呼び出した関数中で、まるでその関数が直接デリゲートが参照している関数を呼び出したかのように続けられることになります。
呼び出しリストに複数のエントリを含むデリゲートインスタンスの呼び出しは、呼び出しリスト内のそれぞれの関数を、同時に、リストの順に、呼び出していきます。
それぞれの呼び出し関数にはデリゲートインスタンスに与えられた同じ引数のセットが渡されます。
もし、そのようなデリゲート呼び出しのパラメータに非constなアドレスや参照やハンドルを含んでいた場合、それぞれの関数呼び出しはそのまま同じアドレス、参照、ハンドルに対して発生するでしょう。
呼び出しリスト中のある関数によるそれらの変数の変化はそれ以降の呼び出しリスト中の関数には見えていることでしょう。
もし、デリゲート呼び出しが返値を含む場合、リスト中の最後のデリゲート呼び出しの最後の値が返されることでしょう。
もし、そのようなデリゲートの呼び出し途中で例外が発生し、その関数が例外を捕らえなかった場合、例外catch節の探索はデリゲートを呼び出した関数中について続けられ、それ以降の呼び出しリスト中の関数は呼び出されません。
]
nullptr値を持つデリゲート・インスタンスの呼び出そうとする試みは、System::NullReferenceException 型の例外を結果とします。