Effective C# 3rd 読書メモ 28 特定の型のコレクションに拡張メソッドを用意することを検討する

List<int>のような、組み込みの型を使用したコレクションで、さらに追加の機能が必要な場合、拡張メソッドを使って実装できる。

たとえば、System.Linq.Enumerableには、IEnumerable<T>だけでなく、型に特化した拡張メソッドも定義されている。以下はIEnumerable<int>に特化したAverage()メソッドである。

Cutomerというクラスのオブジェクトのコレクションに対して専用のクエリを発行するメソッドがほしい、といった場合には、IEnumerable<Customer>を拡張したCustomerListのようなクラスを作成するよりも先に、拡張メソッドの作成を検討すべきである。
一般に、派生クラスよりも拡張メソッドのほうが再利用性が高い。

Effective C# 3rd 読書メモ 27 最小限のインターフェイスを拡張メソッドで増強する

拡張メソッドを使うと、インターフェイスに機能を追加することができる。

System.Linq.Enumerableクラスは拡張メソッドの好例である。System.Enumerableには、IEnumerable<T>に対する、50以上の拡張メソッドが含まれる。

インターフェイスは最小限の機能のみを定義し、追加の機能は拡張メソッドで定義すべきである。

なお、拡張メソッドと同じシグネチャのメソッドがクラスのインスタンスメソッドとして定義されている場合、インスタンスメソッドの方が優先される。

参考:拡張メソッド

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)

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

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

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

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