C# ジェネリック制約(where 句)
ジェネリック制約(where 句)を使うと、型パラメータに条件を設けることができます。制約を付けることで、特定のメソッドやプロパティが使えることを保証し、より安全なコードが書けます。
制約がないと何が問題か
制約のない型パラメータ T は、どんな型でも受け入れます。そのため、T に対して使えるメソッドは object のものだけです。
// 制約なし:T のメソッドを呼べない
public void Process<T>(T item)
{
// item.SomeMethod(); // エラー:T に SomeMethod があるとは限らない
Console.WriteLine(item?.ToString()); // object のメソッドなら OK
}where 句で制約を追加
where 句を使うと、型パラメータに条件を付けられます。
public interface IDisplayable
{
string GetDisplay();
}
// T は IDisplayable を実装している型に限定
public void ShowDisplay<T>(T item) where T : IDisplayable
{
Console.WriteLine(item.GetDisplay()); // GetDisplay を呼べる
}制約の種類
C# では様々な制約を指定できます。
where T : class
T は参照型でなければならない
where T : struct
T は値型(null 非許容)でなければならない
where T : new()
T は引数なしのコンストラクタを持たなければならない
where T : BaseClass
T は指定したクラスまたはその派生クラスでなければならない
where T : IInterface
T は指定したインターフェースを実装していなければならない
複数の制約を組み合わせる
一つの型パラメータに複数の制約を付けることもできます。
public class Repository<T> where T : class, IEntity, new()
{
public T Create()
{
return new T(); // new() 制約があるので可能
}
public void Save(T entity)
{
Console.WriteLine($"ID: {entity.Id}"); // IEntity のメンバーにアクセス可能
}
}
public interface IEntity
{
int Id { get; }
}複数の型パラメータへの制約
複数の型パラメータがある場合、それぞれに制約を付けられます。
public class Mapper<TSource, TDestination>
where TSource : class
where TDestination : class, new()
{
public TDestination Map(TSource source)
{
var dest = new TDestination();
// マッピング処理
return dest;
}
}new() 制約の活用
new() 制約を使うと、ジェネリックメソッド内でインスタンスを生成できます。
public static T CreateInstance<T>() where T : new()
{
return new T();
}
var list = CreateInstance<List<int>>();
var dict = CreateInstance<Dictionary<string, int>>();制約なし
object のメンバーしか使えない。型の性質を仮定できない。
制約あり
指定した型やインターフェースのメンバーが使える。意図が明確。
制約を適切に使うことで、ジェネリクスの柔軟性と型安全性を両立できます。コンパイル時に不正な型の使用を検出できるため、バグの早期発見にも役立ちます。