次の方法で共有


型パラメータの制約 (C# プログラミング ガイド)

更新 : 2008 年 7 月

ジェネリック クラスを定義するときは、クライアント コードでクラスをインスタンス化するときに型引数に使用できる型の種類に制限を適用できます。クライアント コードで、制限で許可されていない型を使ってクラスをインスタンス化しようとすると、コンパイル時にエラーが発生します。このような制限を制約と呼びます。制約は、where コンテキスト キーワードで指定します。6 種類の制約を次に示します。

制約

説明

where T: 構造体

型引数は、値型である必要があります。Nullable 以外のすべての値型を指定できます。詳細については、「Null 許容型の使用 (C# プログラミング ガイド)」を参照してください。

where T : クラス

型引数は、参照型である必要があります。このことは、クラス型、インターフェイス型、デリゲート型、配列型についても当てはまります。

where T : new()

型引数は、パラメータなしのパブリック コンストラクタを持つ必要があります。new() 制約を別の制約と併用する場合、この制約を最後に指定する必要があります。

where T : <基本クラス名>

型引数は、指定した基本クラスであるか、または指定した基本クラスから派生する必要があります。

where T : <インターフェイス名>

型引数は、指定したインターフェイスであるか、または指定したインターフェイスを実装する必要があります。インターフェイス制約は複数指定できます。制約元のインターフェイスもジェネリックにできます。

where T : U

T の位置にある型引数は、U の位置にある引数であるか、またはその引数から派生する必要があります。この制約は、生の型制約と呼ばれます。

制約を使用する理由

ジェネリック リストの項目をチェックして、その項目が有効であるかどうかを確認したり、他の項目と比較したりする場合は、コンパイラが呼び出す必要がある演算子やメソッドが、クライアント コードで指定される可能性があるすべての型引数でサポートされるというある程度の保証をコンパイラに与える必要があります。この保証が、ジェネリック クラス定義に 1 つ以上の制約を適用することで得られます。たとえば、基本クラス制約では、この型のオブジェクトまたはこの型から派生したオブジェクトだけが型引数として使用されることをコンパイラに伝えます。この保証が得られると、コンパイラは、その型のメソッドをジェネリック クラス内で呼び出すことを許可できます。制約は、where コンテキスト キーワードを使用して適用します。基本クラス制約を適用することによって、「ジェネリックの概要 (C# プログラミング ガイド)」 の GenericList<T> クラスに追加できる機能を次のコード例に示します。

public class Employee
{
    private string name;
    private int id;

    public Employee(string s, int i)
    {
        name = s;
        id = i;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int ID
    {
        get { return id; }
        set { id = value; }
    }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        private Node next;
        private T data;

        public Node(T t)
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    public GenericList() //constructor
    {
        head = null;
    }

    public void AddHead(T t)
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T FindFirstOccurrence(string s)
    {
        Node current = head;
        T t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}

制約により、T 型のすべての項目が Employee オブジェクト、または Employee から継承されたオブジェクトのいずれかであることが保証されるため、ジェネリック クラスは、Employee.Name プロパティを使用できます。

制約は、次のように同じ型パラメータに複数適用でき、制約自体をジェネリック型にできます。

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

型パラメータを制約することで、許容される操作の数と、制約している型とその継承階層内のすべての型でサポートされるメソッドへのメソッド呼び出しの数が増えます。そのため、ジェネリック クラスやジェネリック メソッドを設計するときに、簡単な代入を超える操作を汎用メンバに対して実行したり、System.Object でサポートされていないメソッドを呼び出したりする場合は、型パラメータに制約を適用する必要があります。

where T : class 制約を適用するときは、== 演算子と != 演算子を型パラメータで使用しないでください。これらの演算子は、値の等価性ではなく、参照 ID のみをテストするからです。これらの演算子が、引数として使用される型でオーバーロードされた場合でも同じです。次のコードは、この点を示しています。このコードの出力は、String クラスが == 演算子をオーバーロードしても false です。

public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "foo";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("foo");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}

false が出力されるのは、コンパイル時に、コンパイラが、T が参照型であるということしか認識しないため、すべての参照型に有効な既定の演算子を使用する必要があるからです。値の等価性をテストする必要がある場合も、where T : IComparable<T> 制約を適用し、ジェネリック クラスの構成に使用されるすべてのクラスでそのインターフェイスを実装することをお勧めします。

複数のパラメータに対する制約

次の例に示されているように、複数のパラメータに制約を適用したり、単一のパラメータに複数の制約を適用したりできます。

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new() { }

非バインド型パラメータ

SampleClass<T>{} パブリック クラスの T など、制約を持たない型パラメータは、非バインド型パラメータと呼ばれます。非バインド型パラメータには、次の規則が適用されます。

  • != 演算子と == 演算子は、具象型引数がこれらの演算子をサポートするという保証がないため、使用できません。

  • これらのパラメータは、System.Object との間で変換できます。またはインターフェイス型に明示的に変換できます。

  • null と比較できます。非バインド パラメータを null に比較したときに型引数が値型の場合は、常に false が返されます。

生の型制約

ジェネリック型パラメータを制約として使用した場合、この制約は生の型制約と呼ばれます。生の型制約は、固有の型パラメータを持つメンバ関数で、そのパラメータを、メンバ関数を包含する側の型の型パラメータに制約する必要があるときに便利です。このコード例を次に示します。

class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}

上の例で、T は、Add メソッドのコンテキストでは生の型制約であり、List クラスのコンテキストでは非バインド型パラメータです。

生の型制約も、ジェネリック クラスの定義で使用できます。生の型制約も他の型パラメータと同様に山かっこで囲んで宣言する必要があります。

//naked type constraint
public class SampleClass<T, U, V> where T : V { }

ジェネリック クラスを使用した生の型制約の有効性は非常に限られます。というのも、生の型制約が System.Object から派生したことしかコンパイラが認識できないからです。ジェネリック クラスの生の型制約は、2 つの型パラメータの間に継承関係を適用する場合に使用します。

参照

概念

C# プログラミング ガイド

参照

ジェネリックの概要 (C# プログラミング ガイド)

new 制約 (C# リファレンス)

System.Collections.Generic

履歴の変更

日付

履歴

理由

2008 年 7 月

複数の制約に関する段落を追加

コンテンツ バグ修正