Effective C# 3rd 読書メモ 26 ジェネリックインターフェイスに加えてクラスインターフェイスを実装する

ジェネリックなインターフェイスだけでなく、古典的な非ジェネリックのインターフェイスもサポートすることで、クラスはより便利になる。

たとえば、クラスにオブジェクト間の比較機能をもたせたい場合、IComparable<T>だけでなくIComparableも実装する。

Effective C# 3rd 読書メモ 25 ジェネリッククラスよりもジェネリックメソッドを使う

一般に、ジェネリッククラスよりも、非ジェネリッククラスにジェネリックメソッドが用意されている方が、使い勝手がよい。

上記サンプルでは、UtilsByGenericMethodの方が、 (1) 呼び出し側での型パラメータの指定が不要 (2) doubleに特化した処理を実装しやすく、かつ、オーバーロードの選択がコンパイラ任せなのでパフォーマンスが良い という点で優れている。

原則として非ジェネリッククラスにジェネリックメソッドを定義し、以下の場合にのみジェネリッククラスを定義すべきである。
(1) 型パラメータにより指定される型の値をクラスが保持する場合(Collection等)
(2) クラスがジェネリックインターフェイスを実装する場合

Effective C# 3rd 読書メモ 24 ベースクラス・インターフェイスを使ってジェネリックの特殊化をしない

ジェネリックメソッドを定義する際には、オーバーロードの扱いに注意が必要である。

ジェネリックメソッドは、ベースクラスがマッチするメソッドよりも優先度が高い。以下の2つのメソッドがある場合に呼び出されるオーバーロードは以下の通り。

  • パラメータがMyBaseオブジェクト => WriteMessage(MyBase)
  • パラメータがMyBaseの派生クラスのオブジェクト => WriteMessage(T)

このルールはインターフェイスにも同様に適用される。

一般に、あるクラスとその子孫全てをサポートするために、ベースクラスに対してジェネリックの特殊化を行うことは、良い考えではない。インターフェイスも同様である。

以下のようにメソッド内で型をチェックして処理を分けることもできるが、最適な方法ではない。

この実装には実行時のオーバーヘッドが発生するという問題があるため、このような特殊化は 明らかにより良い機能を提供できる場合 かつ パフォーマンス上の悪影響が無視できる程度である場合 にのみ行うべきである。

Effective C# 3rd 読書メモ 23 デリゲートを使ってメソッド用の型パラメータの制約を定義する

C#のジェネリックスの型パラメータの制約には以下の種類がある。

  • ベースクラス(1つだけ)
  • インターフェイス(複数可)
  • クラスまたは構造体
  • パラメータのないコンストラクタ

この制約だけでは、パラメータが特定のメソッドを持つこと、といった制約を行うことはできない(インターフェイスを使えばメソッド単位の制約も実現できるが、実装が面倒)。

しかし、デリゲートのシグネチャを利用すると、ジェネリッククラスが必要とするメソッドを指定することができる。

このAdd()メソッドは以下のように利用できる。

Effective C# 3rd 読書メモ 22 ジェネリックの共変性と反変性をサポートする

共変性(covariance)と反変性(contravariance)は、ある型が別の型に変換可能な性質をもっている、ということを意味する。可能なときはいつでも、ジェネリックなインターフェイスを装飾して、ジェネリックの共変性と反変性をサポートできるようにすべきである。

  • 共変性: 継承関係において、より上方の型に変換可能である
  • 反変性: 継承関係において、より下方の型に型に変換可能である

C# 4.0より前では、全てのジェネリック型は不変(invariant)だった。C# 4.0以降では、新しいキーワードを使うことで、共変性と反変性をサポートすることができる。

パラメータにout修飾子をつけると、 (1) メソッドの戻り値 (2) プロパティのgetアクセサ にしか使えなくなる。out修飾子による制限のため、Tの内容は変更されることがないため、より上方の型への変換は安全に行うことができる。

以下は、out修飾子を使用しているIEnumerator<T>の例である。

以下のように、Enumerator<T>には共変性がある(ベース型に変換できる)。

逆に、in修飾子を使うことで、メソッドのパラメータにしか使えないように制限することができる。

以下のように、Comparer<T>には反変性がある(派生型に変換できる)。

参考:ジェネリックの共変性と反変性