web-dev-qa-db-ja.com

C#EventHandlerは間違った方法で設計されていますか?

労働組合の状態:

C#イベント/イベントハンドラは次のとおりです。

  • ブロッキング
  • 例外を投げる
  • 一連の
  • 確定的に実行される
  • MulticastDelegates
  • ハンドラーは常に、以前に登録されたハンドラーの動作に依存しています

実際、それらは通常の関数ポインタにかなり近いです。それらのシーケンスを蜂蜜にもかかわらず。

イベントサブスクライバーが悪質なことを行っている場合(ブロック、例外のスロー):

  • イベント呼び出し元がブロックされている(最悪の場合は無期限)
  • イベントの呼び出し側は、予測できない例外を処理する必要があります
  • 内部順次イベントハンドラー呼び出しは最初の例外で中断します
  • 失敗したEventhandlerの後に内部的に保存されたEventHandlerは実行されません

。Net FiddleのC#の例

私は常にc#イベントをpublish-subcribeパターンの実装と考えていました。

しかし:

これは、パブリッシング/サブスクライブのセマンティクスの私の直観と矛盾します。実際は逆のようです。

ニュース/ウェブサイト/本/ポッドキャスト/ニュースレターを公開する場合:

  • 公開は非ブロッキングです(サブスクライバーとの関係で)
  • 消費は同時です
  • リーダー/サブスクライバーのエラーは私の出版を妨げません
  • リーダー/サブスクライバーエラーは他のリーダー/サブスクライバーに干渉しません

.Netに転送すると、次のことを意味します。event.Invoke(...)は次のようになります。

  • event.Invoke(...)は起動して忘れる
  • すべてのサブスクリプションがスレッドプールにディスパッチされます
  • 同時に実行され、互いに独立して実行されます(ただし、スレッドセーフではありません)。
  • 不確定な実行順序
  • オブジェクトにアクセスするときはスレッドセーフに注意する必要があるかもしれません
  • 1つのハンドラーが他のハンドラーの実行を「強制終了」することはできません

他の人々も混乱しているようです:

PS:私はこれが非常に主観的であるかもしれないことを知っています。このようにするのには十分な理由があると思います。

6
juwens

あなたの批判には2つの主な目的があるようです:

  • 敵対的またはバグのある購読者に直面すると、イベントは壊れやすくなります。
  • パブリッシュ/サブスクライブのメタファーがあなたの直感と一致しません。

最初に2番目の点を取り上げましょう。デザインパターンのすべてのメタファーはアナロジーであり、同型ではありません。批判は妥当ですが、デザインは、メタファーが伴う直感と一致したいという欲求によって動機付けられなかったことを思い出してください!この設計は、基幹業務の開発者のニーズに適度なコストで応えたいという欲求によって動機付けられました。

最初のポイントはより重要なものです。 敵対的またはバグのあるサブスクライバーが直面するイベントは確かに壊れやすい。いくつかの興味深い攻撃があります。

たとえば、異なる信頼レベルのコードを同時に「スタックに」置くことができるように設計された.NET 1.0セキュリティモデルを考えてみてください。敵対的なことを行う-スタックウォークによって緩和されます。つまり、高信頼コードが危険な行為を試みる場合、高信頼コード自体だけでなく、コールスタック上のすべてのコードが十分に信頼されている必要があります。

では、信頼度の低いコードが、信頼度の高いメソッドにデリゲートをイベントのハンドラーとして追加するとどうなるでしょうか。イベントがトリガーされると、低信頼コードはスタック上にないため、スタックウォークはそれを認識しません。

悪意のあるコードがイベントを使用してユーザーに損害を与える方法は数多くあります。 イベントは特にこれらの脆弱性を軽減するように設計されていません。開発者は、信頼性の高いコードのみがイベントハンドラーの追加を許可され、ハンドラーが無害であることを保証するコードを作成する責任があります。

あなたの質問は、「デザインですか間違っています?」ええと、答えは、設計目標を達成できない場合、設計が間違っているということです。敵対的なイベントハンドラーに直面してイベントが堅牢なシステムを設計することは、設計プロセスの目標ではないことを明確に示したので、設計はその意味では「間違った」ものではありません。

非同期、分離、堅牢性など、必要なプロパティを持つパブリッシュ/サブスクライブシステムを開発することは確かに可能です。デフォルトのイベントシステムは、これらのプロパティを持つようには設計されていません。むしろ、ボタンをクリックするとチェックボックスが緑色になるように設計されています。それはその目標に非常によく成功します。

さらに、あなたの好みが普遍的に「より良い」ことは決して明らかではありません。特定のユースケースでのみ優れている場合があります。議論のために、そのうちの1つを見てみましょう。 goodあるハンドラーによってスローされた例外が次のハンドラーの実行を妨げているのでしょうか、それともbadですか?あなたはそれがbadであることを示唆していますが、その前提をより注意深く調べています。ハンドラーが未処理の例外を発生させると、どのような状況で期待できますか?

  • ハンドラーは敵対的で、サービス拒否攻撃を試みています。

この状況では、[]が攻撃である可能性があるハンドラコードをさらに実行しようとするために正しいことは何ですか? 依存するである可能性のある攻撃は、クラッシュを引き起こした、壊れた状態が生成された場合に発生しますか?

  • ハンドラーは無害ですがバグがあり、誤ってクラッシュしました

ハンドラーはバグが多いため、クラッシュし、独自の内部状態が不整合のままになり、ユーザーデータが失われる可能性があります。 secondハンドラを実行するために正しいことはありますalsoはその内部状態に依存し、moreユーザーデータを失う可能性がありますか?

  • ハンドラーは無害でバグがなく、そのアクションはイベントソースのクラッシュするバグを公開します。

イベントソースの内部データ構造が破損しているため、イベント処理中に予期しない例外がスローされます。実行するために正しいことはmore code


予期しない事態が発生し、世界が危険なほど不安定で予測不可能な状態になった場合、more random codeを実行することはほとんどの場合wrongを実行することです。クラッシュを分離しても、壊れたシステムが存続するだけで害が発生するlongerより多くの害が発生する! rightイベントハンドラーが予期しない例外をスローしたときに行うことは、システム全体をシャットダウンし、それ以上の損傷を停止し、問題をログに記録し、開発チームにバグの多いパッチを適用してコードをクラッシュさせることです。

17
Eric Lippert

C#イベントは、ユーザーインターフェイスがアクションに応じて更新する必要があるユーザーインターフェイス用に設計されました。そのコンテキストでイベントを使用している場合、それらは「十分十分」です。たとえば、モデルはFooベースのコントロールにバインドされているObservableList<Foo>プロパティに新しいItemTemplate要素を追加します。フロントエンドコードは、リスト変更イベントをリッスンするようにすでに装備されているため、Foo要素のテンプレートの新しいインスタンスを作成できます。これは機能し、テンプレートが壊れていない限り、フロントエンドコードは例外をスローしません。

真のpub/subコンセプトが必要な場合は、イベントバスまたはメッセージキューが必要です。これらは、パブリッシュ/サブスクライブのために設計された目的です。ここで、EventBusとMessage Queueは、C#イベントが作成された目的のための誤ったソリューションであることを述べることが重要です。

つまり、C#イベントを誤って考えていることになります。

10
Berin Loritsch

非同期であることは、同期よりも常に優れているとは限りません。大多数のデスクトップシナリオに適した、平均的で無駄のない直接的な実装が得られます。非同期動作が必要な場合、必要に応じて追加することを妨げるものはありません。また、オブジェクト開発者がサブスクライバーによってスローされた例外を処理するのを妨げるものはありません。

Webのコンテキストでは選択の余地はありません。開発者として育ち、慣れているようです。メッセージが受信されたかどうかわからないことは、デスクトップ開発者にとってかなりぎこちない感じです。それはあなたがどこから来たのかによります。

典型的なデスクトップシナリオでは、誰がイベントを使用可能にしたかに関係なく、ハンドラーを実装するのはあなたです。だから何かが台無しにされた場合、あなたがそれを引き起こし、問題はないので、あなたは自分でそれを修正することができます。さらに、アプリケーションをロックしたり、例外をスローしたりするためのイベントは必要ありません。

0
Martin Maat