bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] ジェネリックの型制約(Constraining Type Parameters)入門 — where T : struct / class / new() / 基底クラス / インターフェース

[C#] ジェネリックの型制約(Constraining Type Parameters)入門 — where T : struct / class / new() / 基底クラス / インターフェース

Jun 3, 2026 C# , .NET bucket-sort

C# のジェネリクスを書いていると、List<T> の T のように 「何でも入る型パラメーター」 だけでは困る場面が出てきます。たとえば「T のインスタンスを new T() で作りたい」「T を null と比較したい」「T の Dispose() を呼びたい」など、T に対して何らかの能力を仮定したい とき、コンパイラに「T はこういう型に限る」と教えてあげる必要があります。

そのための仕組みが 型制約(Constraining Type Parameters) で、ジェネリック宣言の末尾に where T : ... という形で書きます。

この記事では、以下の代表的な 5 つの制約を取り上げ、それぞれが何を意味するのか・いつ使うのか・どう書くのか を整理します。

  • where T : struct
  • where T : class
  • where T : new()
  • where T : NameOfBaseClass
  • where T : NameOfInterface

なぜ型制約が必要なのか

制約のないジェネリックでは、T はあらゆる型に化け得るため、コンパイラは T について object が持っているメンバーしか 信用しません。

public static T CreateDefault<T>()
{
    return new T(); // ❌ コンパイルエラー: T が引数なしのコンストラクタを持つとは限らない
}

T に「引数なしコンストラクタを持つ型に限る」という制約を付ければ、このコードは通るようになります。

public static T CreateDefault<T>() where T : new()
{
    return new T(); // ✅ OK
}

このように、型制約は 「T にこれだけの能力があると約束するから、それを使った操作を許可してくれ」 とコンパイラに伝えるためのものです。同時に、呼び出し側にも「この型パラメーターには制約を満たす型しか渡せない」という保証が与えられます。

where T : struct — 値型に限定

T を 値型(struct、enum、プリミティブ型など) に限定する制約です。Nullable<T>(int? など)は 含まれません。

public static T Sum<T>(T a, T b) where T : struct
{
    // T は値型だが、+ 演算子があるとは限らないので注意(後述)
    return a; // ここでは型制約の例として
}

何が嬉しいのか

  • T のデフォルト値は 常に意味のあるゼロ値(default(T) が null にならない)
  • Nullable<T> の型パラメーターに使える(Nullable<T> 自体が where T : struct を要求している)
  • ボックス化を避けやすい設計になる

典型的な使いどころ

Nullable<T> のように 「値型ラッパー」 を作るケースです。

public readonly struct Maybe<T> where T : struct
{
    public bool HasValue { get; }
    public T Value { get; }

    public Maybe(T value)
    {
        HasValue = true;
        Value = value;
    }
}

var m = new Maybe<int>(42);
// var m2 = new Maybe<string>("x"); // ❌ string は参照型なので渡せない

注意点

where T : struct を付けても、T が + や * などの演算子を持つ保証はありません。算術演算を行いたい場合は C# 11 以降の 静的抽象メンバー(INumber<T> などの汎用数値インターフェース) を使うのが現代的です。

where T : class — 参照型に限定

T を 参照型(クラス、インターフェース、デリゲート、配列など) に限定する制約です。

public sealed class WeakRef<T> where T : class
{
    private readonly WeakReference<T> _ref;

    public WeakRef(T target) => _ref = new WeakReference<T>(target);

    public T? Get() => _ref.TryGetTarget(out var t) ? t : null;
}

何が嬉しいのか

  • T を null と比較できる(t == null、t is null)
  • T? のように null 許容参照型 として扱える
  • WeakReference<T> のように 参照型でしか意味を持たない API に渡せる

where T : class? との違い(nullable 対応)

null 許容参照型を有効にしたコンテキストでは、次の 2 つを区別します。

制約 意味
where T : class 非 null な参照型に限る
where T : class? null 許容も含めた参照型に限る

呼び出し側が string? を渡せるようにしたいなら class?、null を許さない API として設計したいなら class を選びます。

典型的な使いどころ

  • キャッシュやリポジトリなど 参照を保持してから使う 系のクラス
  • null を「未設定」「未取得」のセンチネル値として使いたいケース
  • event ハンドラのレジストリのように デリゲート(参照型) を扱う基盤

where T : new() — 引数なしコンストラクタを持つ型

T が public で引数なしのコンストラクタを持つ ことを要求する制約です。これを付けると new T() がジェネリックメソッド内で書けるようになります。

public static class Factory
{
    public static T Create<T>() where T : new()
    {
        return new T();
    }
}

var list = Factory.Create<List<int>>(); // ✅ List<int> は引数なしコンストラクタを持つ
// var sb = Factory.Create<string>();   // ❌ string は引数なしコンストラクタを持たない

注意点

  • new() 制約は 引数なし のコンストラクタしか作れません。引数付きで作りたい場合は ファクトリデリゲート(Func<T>)を渡す のが定石です。
  • abstract クラスは new() 制約を満たしません。
  • 構造体は常に「引数なしコンストラクタを暗黙に持つ」とみなされるため、new() を満たします。

他の制約との順序ルール

new() は 常に最後 に書く必要があります。

// ✅ OK
public class Repo<T> where T : DbEntity, IIdentifiable, new() { }

// ❌ コンパイルエラー: new() は最後に書く
public class Repo<T> where T : new(), DbEntity { }

where T : NameOfBaseClass — 特定の基底クラスを継承

T が 指定したクラス、またはそれを継承するクラス であることを要求します。

public abstract class Animal
{
    public abstract string Cry();
}

public sealed class Dog : Animal
{
    public override string Cry() => "ワン";
}

public static class Zoo
{
    public static void Shout<T>(T animal) where T : Animal
    {
        // T は Animal なので、Animal のメンバーが直接呼べる
        Console.WriteLine(animal.Cry());
    }
}

Zoo.Shout(new Dog()); // ✅
// Zoo.Shout("string"); // ❌ string は Animal を継承していない

何が嬉しいのか

  • T を 基底クラスとして安全に扱える(キャスト不要でメンバーにアクセス可能)
  • 受け取った T をそのまま返すような API で、呼び出し側の具体型を保ったまま ジェネリックに扱える

T で受ける vs. 基底クラスで受ける

「単に基底クラスのメンバーを使いたいだけ」なら、わざわざジェネリックにせず引数を Animal 型で受ければ十分です。ジェネリックにする意味は次のような場合です。

// 受け取った T をそのままの型で返したい
public static T Echo<T>(T animal) where T : Animal => animal;

Dog d = Echo(new Dog()); // ✅ Dog のまま返ってくる(Animal にダウングレードされない)

注意点

  • sealed クラスを基底制約に指定することは できません(継承できない型を制約にしても、T がその型自身に固定されてしまうため意味がない)。
  • System.Object、System.Array、System.Delegate などの特殊な基底型は制約に使えません(一部は新しい C# で緩和されています)。

where T : NameOfInterface — 特定のインターフェースを実装

T が 指定したインターフェースを実装する ことを要求します。基底クラス制約と並んで、もっとも実用頻度が高い制約です。

public static T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) >= 0 ? a : b;
}

int m = Max(3, 5);              // ✅ int は IComparable<int> を実装
string s = Max("apple", "pear"); // ✅ string も IComparable<string> を実装

何が嬉しいのか

  • T が持つインターフェースのメンバーを キャストや動的ディスパッチなしで 呼べる
  • 値型を渡しても ボックス化が起きない(インターフェース変数経由で呼ぶより高速)

典型的な使いどころ

  • 比較・順序付け(IComparable<T>、IEquatable<T>)
  • 列挙(IEnumerable<T>)
  • リソース解放(IDisposable)
  • C# 11 以降の 静的抽象メンバー(INumber<T>、IAdditionOperators<T,T,T> など)
using System.Numerics;

public static T Sum<T>(IEnumerable<T> values) where T : INumber<T>
{
    T total = T.Zero;
    foreach (var v in values) total += v;
    return total;
}

制約は組み合わせられる

複数の制約はカンマ区切りで列挙でき、順序にもルール があります。

public class Repository<T>
    where T : class,           // 1. 主要制約(class / struct / 基底クラス)は最初
              IEntity,         // 2. インターフェース制約は中間(複数可)
              IComparable<T>,
              new()            // 3. new() は最後
{
    // ...
}

ルールをまとめると次の通りです。

順序 制約の種類 個数
1 class / struct / 基底クラス(いずれか 1 つ) 0〜1
2 インターフェース制約 0〜複数
3 new() 0〜1

class と struct は 同時に書けません(互いに排他)。struct を指定したときは new() を書く必要はありません(値型は常に引数なしコンストラクタを持つ)。

複数の型パラメーターに制約をかける

型パラメーターが複数あるときは、where 句を 型パラメーターごとに 1 つずつ 書きます。

public class Map<TKey, TValue>
    where TKey : notnull
    where TValue : class, new()
{
    // ...
}

どの制約をいつ使うか — 早見表

やりたいこと 使う制約
T を null と比較したい / null を返したい where T : class
default(T) を意味のあるゼロにしたい / Nullable<T> の中身にしたい where T : struct
new T() でインスタンスを作りたい where T : new()
T を特定の基底クラスのメンバーで操作したい where T : BaseClass
T を比較・列挙・破棄など 能力単位 で扱いたい where T : IInterface
T をそのままの具体型で受け渡ししたい ジェネリック化 + 基底クラス/インターフェース制約

まとめ

  • 型制約は、ジェネリックの T に 「最低限これくらいの能力はある」 とコンパイラに伝える仕組み。
  • struct / class は 値型 / 参照型 の二者択一の主要制約。
  • new() は new T() を許可 する制約で、必ず最後に書く。
  • 基底クラス制約は 継承関係、インターフェース制約は 能力(できること) で T を絞り込む。
  • 複数組み合わせるときは class/struct/基底クラス → インターフェース → new() の順。

ジェネリックは「型に依存しない汎用コード」を書くための仕組みですが、何でも受け入れすぎると何もできない というジレンマがあります。型制約はそのバランスを取り、「必要な能力だけを要求して、それ以外は自由」 という設計を可能にする、ジェネリクスの要となる機能です。

C# .NET ジェネリクス Generics 型制約 Where Constraining Type Parameters
← [C#] System.Collections.ObjectModel. ReadOnlyObservableCollection<T> — 変更通知付きの読み取り専用ビュー

Related Posts

  • [C#] System.Collections.ObjectModel. ReadOnlyObservableCollection<T> — 変更通知付きの読み取り専用ビュー Jun 2, 2026
  • [C#] System.Collections.ObjectModel. ObservableCollection<T> — 変更通知付きコレクションの仕組みと使いどころ Jun 1, 2026
  • [C#] System.Collections.Generic.Stack<T> — 型安全な LIFO スタックの仕組みと使いどころ May 31, 2026
  • [C#] System.Collections.Generic.SortedSet<T> — 赤黒木で実装される整列セット May 30, 2026

Table of Contents

  • なぜ型制約が必要なのか
  • where T : struct — 値型に限定
    • 何が嬉しいのか
    • 典型的な使いどころ
    • 注意点
  • where T : class — 参照型に限定
    • 何が嬉しいのか
    • where T : class? との違い(nullable 対応)
    • 典型的な使いどころ
  • where T : new() — 引数なしコンストラクタを持つ型
    • 注意点
    • 他の制約との順序ルール
  • where T : NameOfBaseClass — 特定の基底クラスを継承
    • 何が嬉しいのか
    • T で受ける vs. 基底クラスで受ける
    • 注意点
  • where T : NameOfInterface — 特定のインターフェースを実装
    • 何が嬉しいのか
    • 典型的な使いどころ
  • 制約は組み合わせられる
  • 複数の型パラメーターに制約をかける
  • どの制約をいつ使うか — 早見表
  • まとめ

Recent Posts

  • [C#] ジェネリックの型制約(Constraining Type Parameters)入門 — where T : struct / class / new() / 基底クラス / インターフェース Jun 3, 2026
  • [C#] System.Collections.ObjectModel. ReadOnlyObservableCollection<T> — 変更通知付きの読み取り専用ビュー Jun 2, 2026
  • [C#] System.Collections.ObjectModel. ObservableCollection<T> — 変更通知付きコレクションの仕組みと使いどころ Jun 1, 2026
  • [C#] System.Collections.Generic.Stack<T> — 型安全な LIFO スタックの仕組みと使いどころ May 31, 2026
  • [C#] System.Collections.Generic.SortedSet<T> — 赤黒木で実装される整列セット May 30, 2026

Categories

  • C#84
  • .NET83
  • AWS27
  • Laravel16
  • Linux15
  • MySQL9
  • Apache8
  • PHP8
  • DynamoDB6
  • セキュリティ6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • .NET Framework1
  • Aurora1
  • Filament1
  • Git1
  • SQS1

Tags

  • C#
  • .NET
  • AWS
  • Laravel
  • コレクション
  • PHP
  • セキュリティ
  • MySQL
  • Linux
  • パフォーマンス
  • Apache
  • System.Collections.Generic
  • Code Snippet
  • DynamoDB
  • NoSQL
  • PHP-FPM
  • RDS
  • System.Collections
  • DoS
  • Nginx
  • Windows
  • WordPress
  • メモリ管理
  • 監視
  • 設計
  • Amazon Linux 2023
  • Docker
  • IDisposable
  • Ipset
  • Iptables
  • OPCache
  • System.Collections.Specialized
  • Webサーバー
  • オブジェクト指向
  • クラス設計
  • デザインパターン
  • パターンマッチング
  • 継承
  • 認可
  • Aurora
  • Blade
  • Grafana
  • Hugo
  • InfluxDB
  • Policy
  • Record
  • SSG
  • WPF
  • インターフェース
  • エラーハンドリング
Powered by Hugo & Explore Theme.