21

.NET にはイベントの標準パターンがあります。それらは、delegatesender と呼ばれるプレーンなオブジェクトを受け取る型を使用し、次に .NET から派生する必要がある 2 番目のパラメーターで実際の「ペイロード」を使用しますEventArgs

派生元の 2 番目のパラメーターの理論的根拠EventArgsは非常に明確です ( .NET Framework Standard Library Annotated Referenceを参照してください)。ソフトウェアの進化に合わせて、イベント シンクとソース間のバイナリ互換性を確保することを目的としています。すべてのイベントに対して、引数が 1 つしかない場合でも、その引数を含む 1 つのプロパティを持つカスタム イベント引数クラスを派生させます。これにより、既存のクライアント コードを壊すことなく、将来のバージョンでさらに多くのプロパティをペイロードに追加する機能を保持できます。 . 独自に開発されたコンポーネントのエコシステムにおいて非常に重要です。

しかし、引数がゼロの場合も同じことがわかります。これは、最初のバージョンで引数のないイベントがあり、次のように書くことを意味します。

public event EventHandler Click;

……では、やり方が間違っています。将来、デリゲート タイプをペイロードとして新しいクラスに変更すると、次のようになります。

public class ClickEventArgs : EventArgs { ...

... クライアントとのバイナリ互換性を壊します。add_Clickクライアントは、 を受け取る内部メソッドの特定のオーバーロードにバインドされてしまいEventHandler、デリゲート タイプを変更すると、そのオーバーロードが見つからないため、MissingMethodException.

では、便利な汎用バージョンを使用するとどうなるでしょうか。

public EventHandler<EventArgs> Click;

いいえ、まだ間違っています。なぜなら、 anは anEventHandler<ClickEventArgs>ではないからEventHandler<EventArgs>です。

したがって、 の利点を得るには、そのまま使用するのではなく、派生するEventArgs必要があります。そうでない場合は、それを使用していない可能性があります (私にはそう思われます)。

次に、最初の引数sender. それは不浄なカップリングのレシピのように私には思えます。イベントの発生は、基本的に関数呼び出しです。一般的に言えば、この関数には、スタックを掘り返して呼び出し元を見つけ出し、それに応じて動作を調整する機能が必要ですか? インターフェイスがこのように見えることを強制する必要がありますか?

public interface IFoo
{
    void Bar(object caller, int actualArg1, ...);
}

結局のところ、の実装者は、追加情報を照会できるように、Bar誰が誰であるかを知りたいと思うかもしれません! caller私はあなたが今までに吐いていることを願っています。イベントごとに異なる必要があるのはなぜですか?

したがって、EventArgs宣言するすべてのイベントに対してスタンドアロンの派生クラスを作成するという苦労を覚悟していたとしても、使用するだけの価値があるようにするためにEventArgs、オブジェクトの送信者引数を削除することをお勧めします。

Visual Studio のオートコンプリート機能は、イベントに使用するデリゲートを気にしないようです。+= [hit Space, Return]と入力すると、たまたまデリゲートに一致するハンドラー メソッドが書き込まれます。

では、標準パターンから逸脱すると、どのような価値が失われるのでしょうか?

おまけの質問として、C#/CLR 4.0 は、おそらくデリゲートの反変性を介して、これを変更するために何かを行いますか? これを調査しようとしましたが、別の問題が発生しました。私はもともとこの質問の側面を他の質問に含めていましたが、そこで混乱を引き起こしました。そして、これを全部で 3 つの質問に分割するのは少し多すぎるように思えます...

アップデート:

この問題全体に対する反変性の影響について疑問に思ったのは正しかったことがわかりました!

他の場所で述べたように、新しいコンパイラ ルールは型システムに穴を残し、実行時に爆発します。EventHandler<T>とは異なる定義を行うことで、穴が効果的に塞がれていAction<T>ます。

したがって、イベントの場合、そのタイプの穴を避けるために、 を使用しないでくださいAction<T>。それはあなたが使用しなければならないという意味ではありませんEventHandler<TEventArgs>; ジェネリック デリゲート型を使用する場合は、反変性が有効になっているものを選択しないでください。

4

2 に答える 2

2

イベントハンドラーパターンも好きではありません。私の考えでは、Sender オブジェクトはそれほど役に立ちません。イベントがオブジェクトに何かが発生したことを示している場合 (変更通知など)、EventArgs に情報があるとより役立ちます。Sender の唯一の用途は、イベントの購読を解除することですが、どのイベントを購読解除する必要があるかは必ずしも明確ではありません。

ちなみに、ドラザーがいた場合、Event は AddHandler メソッドと RemoveHandler メソッドではありません。サブスクリプション解除に使用できる MethodInvoker を返す AddHandler メソッドにすぎません。Sender 引数ではなく、最初の引数をサブスクリプション解除に必要な MethodInvoker のコピーにします (オブジェクトが、サブスクリプション解除インボーカーが失われたイベントを受信して​​いることに気付いた場合)。標準の MulticastDelegate は、イベントのディスパッチには適していません (各サブスクライバーは異なるサブスクリプション解除デリゲートを受け取る必要があるため) が、サブスクライブ解除イベントでは、呼び出しリストを介した線形検索は必要ありません。

于 2010-12-18T03:39:56.480 に答える