ジェネリクスを使用すると、型によってパラメータ化されたクラスとメソッドを記述でき、型安全性と再利用性をコードの重複やキャストなしで提供します。制約はジェネリック型パラメータが何である可能性があるかを制限し、そのメンバーの安全な使用を可能にします。ジェネリクスはC#に広く浸透しており(コレクションライブラリ全体がそれを使用しています)。
ジェネリクスが解く問題
=> arr[i];
=> arr[i];
x = Get([] { , , }, );
ジェネリクスは型エラーをランタイム(objectのキャスト)からコンパイル時に移動し、キャストを排除します — 安全で再利用可能なコードです。
public class Box<T> // a generic class
{
private T _value;
public void Set(T value) => _value = value;
public T Get() => _value;
}
var box = new Box<string>(); // T = string, type-safe
// generic method
public T Max<T>(T a, T b) where T : IComparable<T> // constraint (see below)
=> a.CompareTo(b) > 0 ? a : b;
where T : class // T must be a reference type
where T : struct // T must be a value type
where T : new() // T must have a parameterless constructor
where T : IComparable<T> // T must implement an interface (so you can call its members)
where T : BaseClass // T must derive from BaseClass
// example: constraint lets you call CompareTo safely
public T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) > 0 ? a : b; // CompareTo is available because of the constraint
制約(where T : ...)はTが何であるかを制限し、その型のメンバーを安全に使用できます(例えばIComparable<T>に制約するとCompareToを呼び出せます)。制約がない場合、Tは何でもあり得るため、メンバーを仮定できません。
List<T>, Dictionary<K,V>, IEnumerable<T>, Task<T>, Nullable<T> ...
→ the entire collections library and much of .NET is generic.
ジェネリクスはC#の基盤です — コレクションライブラリ全体、Task<T>、IEnumerable<T>、および数えきれないAPIがジェネリクスを使用しているため、それらを理解することはC#開発に欠かせません。
ジェネリクスは実際の問題を解決します:型安全性を保ちながら複数の型に対応する再利用可能なコードを記述する — 代替案(objectを使用してキャストする)はコンパイル時の安全性を損ない、エラーが発生しやすいキャストが必要になり、型ごとにコードを重複させることは保守不可能です。
ジェネリクスはキャストなしで再利用可能で型安全なコードを提供し、コンパイル時のエラーチェックが行われます。制約(where T : ...)は重要な補完機能です:型パラメータが何であるかを制限し、その型のメンバーの安全な使用を可能にし(インターフェースに制約するとそのメソッドを呼び出せます)、正しい使用を保証します。
ジェネリククラスとメソッド、型推論の仕組み、制約(class、struct、new()、インターフェース/基底クラス制約)を理解することは、ジェネリック対応の豊富な.NETライブラリを効果的に使用する場合と、独自の再利用可能で型安全な抽象化を記述する場合の両方で必要です。
ジェネリクスはC#のコレクションと多くのフレームワークの基盤であり、再利用可能で型安全なコードの鍵であるため、ジェネリクスと制約をマスターすることはC#開発に欠かせない、頻繁に適用されるナレッジであり、型安全で再利用可能な設計の理解を反映する一般的なトピックです。