この記事は「Effective C# 6.0/7.0」の読書メモとして、私的プラクティスをまとめています。特に重要だと感じた項目のみ簡潔にまとめています。より詳細な内容に興味のある方は、原著を読んでみることをお勧めします。
項目 18 最低限必須となる制約を常に定義すること
ジェネリックに対して必要な制約があれば、それを指定しましょう。
// NG: 実行時に判別しなくてもいい処理
public static bool AreEqual<T>(T left, T right)
{
if (left == null)
{
return right == null;
}
if (left is IComparable<T>)
{
var lval = left as IComparable<T>;
// ...
}
}
// OK: コンパイル時に判別できる
public static bool AreEqual<T>(T left, T right) where T : IComparable<T>
{
return left.CompareTo(right) == 0;
}
項目 19 実行時の型チェックを使用してジェネリックアルゴリズムを特化する
実行時、ジェネリックの種類によって処理を分岐させたい場合は、型チェックを使用します。
public void DoSomething<T>(T x)
{
if (x is int)
{
// ...
}
if (x is string)
{
// ...
}
}
項目 22 ジェネリックの共変性と反変性をサポートする
C# では in/out 修飾子を使用することにより、ジェネリック引数に共変性・反変性の性質を持たせることができます。
- get あるいは戻り値でしか型引数が使用されないとき :
in
により共変性に対応可能 - set あるいは引数でしか型引数が使用されないとき :
out
により反変性に対応可能
// 共変性に対応したジェネリクス型引数
public interface IEnumerator<out T>
{
// get(=出力のみ)
T Current { get; }
bool MoveNext();
void Reset();
}
// 共変性により string -> object に代入可能
IEnumerator<string> strEnum = new Enumerator<string>();
IEnumerator<object> objEnum = strEnum;
// 反変性に対応したジェネリクス型引数
public interface IComparer<in T>
{
// Tは引数としてしか使われない
int Compare(T a, T b);
}
// 反変性により object -> string に代入可能
IComparer<object> objComp = new Comparer<object>();
IComparer<string> strComp = objComp;
項目 27 最小限に制限されたインターフェースを拡張メソッドにより機能拡張する
拡張メソッドを使用することにより、インターフェースメソッドのデフォルト動作を定義することができます。また、外部から提供されたインターフェースの機能を拡張することができます。
// IComparable<T>のメソッドを拡張する
public static class Comparable
{
public static bool LessThan<T>(this T left, T right) where T : IComparable<T>
{
return left.CompareTo(right) < 0;
}
public static bool GreaterThan<T>(this T left, T right) where T : IComparable<T>
{
return left.CompareTo(right) > 0;
}
}
項目 28 構築された型に対する拡張メソッドを検討すること
独自の型がジェネリックにしたときの拡張メソッドを定義すると便利になることがあります。
public static class MyClassExtension
{
// IEnumerableなMyClassに対しての拡張メソッド
public static int Average(this IEnumerable<MyClass> sequence);
}
// こう書けて便利
var myClassList = new List<MyClass>();
int average = myClassList.Average();