web-dev-qa-db-ja.com

TEventArgsが.NETエコシステムの標準イベントパターンで反変にならないのはなぜですか?

.NETの標準のイベントモデルについてさらに学ぶと、C#でジェネリックスを導入する前に、イベントを処理するメソッドが次のデリゲート型で表されていることがわかりました。

//
// Summary:
//     Represents the method that will handle an event that has no event data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains no event data.
public delegate void EventHandler(object sender, EventArgs e);

しかし、ジェネリックがC#2で導入された後、このデリゲート型はジェネリックを使用して書き換えられたと思います。

//
// Summary:
//     Represents the method that will handle an event when the event provides data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains the event data.
//
// Type parameters:
//   TEventArgs:
//     The type of the event data generated by the event.
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

ここで2つの質問があります。

まず、なぜTEventArgstypeパラメータが作成されなかったのか反変

私が間違っていない場合は、デリゲートのシグネチャで仮パラメーターとして表示される型パラメーターと、デリゲートシグネチャーの共変で戻り型となる型パラメーターを作成することをお勧めします。

Joseph Albahariの本、C#in a Nutshellで、私は引用します:

一般的なデリゲートタイプを定義する場合は、次のことをお勧めします。

  • 戻り値でのみ使用される型パラメーターを共変(out)としてマークします。
  • パラメーターでのみ使用される型パラメーターを反変(in)としてマークします。

そうすることで、型間の継承関係を尊重することにより、変換が自然に機能するようになります。

2番目の質問:TEventArgsがSystem.EventArgsから派生することを強制する一般的な制約がなかったのはなぜですか?

次のように:

public delegate void EventHandler<TEventArgs>  (object source, TEventArgs e) where TEventArgs : EventArgs; 

前もって感謝します。

2番目の質問を明確にするために編集:

TEventArgs(where TEventArgs:EventArgs)の一般的な制約は以前に存在し、Microsoftによって削除されたようですので、設計チームはそれが実際にはあまり意味がありませんでした。

回答を編集して、次のスクリーンショットの一部を含めました

。NET参照ソース

enter image description here

23
Zack ISSOIR

最初に、質問へのコメントのいくつかの懸念に対処するために:世界の誰もがしないことを選択した理由を簡潔に見つけるのは難しいので、私は通常、「なぜしない」の質問に強く反対しますこの作業、およびすべての作業がデフォルトでは行われないため。むしろ、doする理由を見つけ、リソースを他の仕事それを行うことはそれほど重要ではありません。

さらに、特定の会社で働く人々の動機と選択について尋ねるこのフォームの「なぜ」の質問は、おそらくここにいないおそらくその決定をした人々によってのみ答えられるかもしれません。

ただし、この場合は、「なぜ」の質問を閉じるという私の一般的な規則に例外を設けることができます。質問は、これまで書いたことのないデリゲートの共分散に関する重要な点を示しているためです。

イベントデリゲートを変化しないようにする決定はしませんでしたが、そうする立場にあれば、イベントデリゲートを変化しないようにする2つの理由があります。

1つ目は、純粋に「奨励策」のポイントです。イベントハンドラーは通常、特定のイベントを処理するために作成されており、シグネチャに不一致があるデリゲートをハンドラーとして使用することよりも簡単にすることを知っている十分な理由はありません。分散を介して処理されます。イベントハンドラーが処理することになっているイベントにすべての点で正確に一致するイベントハンドラーは、イベント駆動型ワークフローを構築するときに開発者が何をしているのかを開発者が知っているという確信を高めます。

それはかなり弱い理由です。強い理由は悲しい理由でもあります。

ご存知のように、ジェネリックデリゲート型は、戻り値型では共変に、パラメーター型では反変にできます。通常、割り当ての互換性のコンテキストでの差異を考えます。つまり、Func<Mammal, Mammal>を手元に持っている場合は、Func<Giraffe, Animal>型の変数に割り当てて、基になる関数が常に哺乳動物を取ることを知ることができます。 -それは常に動物を返します-それは哺乳類を返すので。

ただし、デリゲートが一緒に追加される場合もあります。デリゲートは不変なので、2つのデリゲートを一緒に追加すると、3番目のデリゲートが生成されます。合計は、加数の順次構成です。

フィールドのようなイベントは、デリゲートの合計を使用して実装されます。これが、イベントにハンドラーを追加することが+=として表される理由です。 (私はこの構文の大ファンではありませんが、今はそれで行き詰まっています。)

これらの機能はどちらも互いに独立してうまく機能しますが、組み合わせた場合はうまく機能しません。デリゲートバリアンスを実装したときに、分散が有効になっている変換のために、基になるデリゲートタイプが一致しないデリゲートの追加に関して、CLRに多数のバグがあることがテストですぐにわかりました。これらのバグはCLR 2.0から存在していましたが、C#4.0までは、主流の言語がバグを公開したことはなく、テストケースも作成されていませんでした。

残念ながら、バグの再現者が何であったか思い出せません。それは12年前のことで、まだディスクにどこかに隠れているメモがあるかどうかはわかりません。

当時、CLRチームと協力して、次のバージョンのCLRでこれらのバグに対処するよう試みましたが、リスクと比較して優先度は十分とは見なされていませんでした。 IEnumerable<T>IComparable<T>などのタイプの多くは、FuncおよびActionタイプと同様に、これらのリリースでバリアント化されましたが、2つを追加することはまれですバリアント変換を使用してFuncsが一致しません。しかし、イベントの代表者にとって、人生における彼らの唯一の目的は一緒に追加されることです。それらは常に一緒に追加され、それらがバリアントであった場合、これらのバグを非常に多くのユーザーに公開するリスクがあったでしょう。

C#4の直後に問題を追跡できなくなったので、正直に言って問題が解決されたかどうかはわかりません。いくつかの一致しないデリゲートをさまざまな組み合わせで追加してみて、何か悪いことが起こっていないか確認してください!

これが、C#4.0リリースの時間枠でイベントデリゲートバリアントをしないのはなぜか、しかし残念な理由です。まだ正当な理由があるかどうかはわかりません。 CLRチームの誰かに尋ねる必要があります。

39
Eric Lippert