項目18 最低限必須となる制約を常に定義すること
ジェネリック型を作成する場合、最低限必須となる制約を常に定義する。
制約
メリット
使用者やコンパイラに対して想定する機能を伝達することができる。
制約があればコンパイラはその制約が満たされている*1として生成するため、使用者がジェネリック型を誤用する場合、コンパイルエラーとして検出できる。
デメリット
使用者は制約を満たす必要があるため、制約が多いと使い勝手の悪いものとなる。
ユーザのコスト(制約を満たすためにかかるコスト)と安全性や製作者のコスト(型を確認するためにかかるコスト)を考え、必要最低限となるような制約を指定すること。
制約がある場合とない場合の例
IComparable<T>
のメソッドを呼び出して比較するメソッド。
制約がない場合
public static bool AreEqual<T>(T left, T right) { //省略 if (left is IComparable<T>) { if (right is IComparable<T>) return left.CompareTo(right) == 0; else //省略(実行時エラー) } else //省略(実行時エラー) }
T
が期待した機能を持っている型*2か確認が必要となる。持っていない場合、実行時エラーとなる。
制約がある場合
public static bool AreEqual<T>(T left, T right) where T : IComparable<T> => left.CompareTo(right) == 0;
メソッド中でT
の確認が不要*3となるため単純化した実装にできる。
制約の個数を最小にする方法
- 制約なしでも利用可能な機能であればそれを制約とはしない
例1)Equals()
IEquatable<T>.Equals()
とSystem.Object.Equals()
が存在する。
IEquatable<T>.Equals()
を使用するほうがメリットが大きいが、System.Object.Equals()
でも十分に機能する(場合がある)。この場合IEquatable<T>
制約を指定するのではなく、IEquatable<T>.Equals()
が利用できるならそれを使用し、利用できないならSystem.Object.Equals()
を使用するようにすればよい(追加の作業(型引数が機能を持つか確認)が必要になる)。
例2)new()
制約
new T()
を使用する代わりにdefault(T)
を使用できるかもしれない。