型パラメーターの制約 (C# プログラミング ガイド)Constraints on type parameters (C# Programming Guide)

制約では、型引数に必要な機能をコンパイラに通知します。Constraints inform the compiler about the capabilities a type argument must have. 制約のない型引数は、任意の型にすることができます。Without any constraints, the type argument could be any type. コンパイラは、.NET 型の最終的な基底クラスになる、Object のメンバーを見なすことができるだけです。The compiler can only assume the members of Object, which is the ultimate base class for any .NET type. 詳細については、「制約を使用する理由」を参照してください。For more information, see Why use constraints. クライアント コードで、この制限で許可されていない型を使用してクラスをインスタンス化しようとすると、コンパイル時エラーが発生します。If client code tries to instantiate your class by using a type that is not allowed by a constraint, the result is a compile-time error. 制約を指定するには、where コンテキスト キーワードを使用します。Constraints are specified by using the where contextual keyword. 次の表では、7 種類の制約を一覧しています。The following table lists the seven types of constraints:

制約Constraint 説明Description
where T : struct この型引数は値の型である必要があります。The type argument must be a value type. Nullable<T> を除く任意の値の型を指定できます。Any value type except Nullable<T> can be specified. null 許容型の詳細については、「null 許容型」をご覧ください。For more information about nullable types, see Nullable types.
where T : class この型引数は参照型である必要があります。The type argument must be a reference type. この制約は、任意のクラス、インターフェイス、デリゲート、または配列型にも適用されます。This constraint applies also to any class, interface, delegate, or array type.
where T : unmanaged この型引数は、参照型である必要はなく、任意の入れ子のレベルに参照型メンバーを含める必要はありません。The type argument must not be a reference type and must not contain any reference type members at any level of nesting.
where T : new() この型引数には、パラメーターなしのパブリック コンストラクターが必要です。The type argument must have a public parameterless constructor. new() 制約を別の制約と併用する場合、この制約を最後に指定する必要があります。When used together with other constraints, the new() constraint must be specified last.
where T : <基底クラス名>where T : <base class name> この型引数は、指定された基底クラスであるか、そのクラスから派生している必要があります。The type argument must be or derive from the specified base class.
where T : <インターフェイス名>where T : <interface name> この型引数は、指定されたインターフェイスであるか、そのインターフェイスを実装している必要があります。The type argument must be or implement the specified interface. 複数のインターフェイス制約を指定することができます。Multiple interface constraints can be specified. 制約のインターフェイスを汎用的にすることもできます。The constraining interface can also be generic.
where T : U T に指定する型引数は、U に指定された引数であるか、その引数から派生している必要があります。The type argument supplied for T must be or derive from the argument supplied for U.

制約の一部は同時に指定できません。Some of the constraints are mutually exclusive. すべての値の型に、アクセス可能なパラメーターなしのコンストラクターがある必要があります。All value types must have an accessible parameterless constructor. struct 制約は new() 制約を意味し、new() 制約は struct 制約と組み合わせることはできません。The struct constraint implies the new() constraint and the new() constraint cannot be combined with the struct constraint. unmanaged 制約は struct 制約を意味します。The unmanaged constraint implies the struct constraint. unmanaged 制約は、struct または new() 制約のいずれかと組み合わせることはできません。The unmanaged constraint cannot be combined with either the struct or new() constraints.

制約を使用する理由Why use constraints

型パラメーターを制約すると、使用できる操作とメソッド呼び出しの数は、制約する型、およびその継承階層内のすべての型でサポートされている数まで増えます。By constraining the type parameter, you increase the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. ジェネリック クラスまたはメソッドを指定するときに、単純な割り当てや、System.Object でサポートされていない任意のメソッド呼び出しでジェネリック メンバーに対して任意の操作を実行する場合、型パラメーターに制約を適用する必要があります。When you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you will have to apply constraints to the type parameter. たとえば、この基底クラスの制約は、この型のオブジェクト、またはこの型から派生したオブジェクトのみを型引数として使用することをコンパイラに指示しています。For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. コンパイラがこの保証を獲得したら、その型のメソッドをジェネリック クラスで呼び出すことができるようになります。Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. 基底クラスの制約を適用して GenericList<T> クラス (「ジェネリックの概要」を参照) に追加できる機能を説明するコード例を次に示します。The following code example demonstrates the functionality you can add to the GenericList<T> class (in Introduction to Generics) by applying a base class constraint.

public class Employee
{
    public Employee(string s, int i) => (Name, ID) = (s, i);
    public string Name { get; set; }
    public int ID { get; set; }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        public Node(T t) => (Next, Data) = (null, t);

        public Node Next { get; set; }
        public T Data { get; set; }
    }

    private Node head;

    public void AddHead(T t)
    {
        Node n = new Node(t) { 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;
    }
}

この制約ではジェネリック クラスで Employee.Name プロパティを使用できるようにします。The constraint enables the generic class to use the Employee.Name property. 制約では、型 T のすべての項目が、Employee オブジェクトまたは Employee から継承するオブジェクトのいずれかになることが保証されることを指定します。The constraint specifies that all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee.

同じ型パラメーターに複数の制約を適用できます。また、制約自体をジェネリック型にすることもできます。次に例を示します。Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:

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

where T : class 制約を適用する場合は、型パラメーターに == および != 演算子を使用しないでください。これらの演算子でテストされるのは、値の等価性ではなく、参照 ID についてのみです。When applying the where T : class constraint, avoid the == and != operators on the type parameter because these operators will test for reference identity only, not for value equality. これらの演算子が、引数として使用されている型内でオーバーロードされている場合でも、この動作が発生します。This behavior occurs even if these operators are overloaded in a type that is used as an argument. この点を説明するコードを次に示します。String クラスが == 演算子をオーバーロードしている場合でも、出力は false です。The following code illustrates this point; the output is false even though the String class overloads the == operator.

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

コンパイラは T がコンパイル時に参照型であることしか認識しておらず、すべての参照型で有効な既定の演算子を使用する必要があります。The compiler only knows that T is a reference type at compile time and must use the default operators that are valid for all reference types. 値の等価性をテストする必要がある場合は、where T : IEquatable<T> または where T : IComparable<T> 制約も適用し、ジェネリック クラスの制約に使用されるすべてのクラスでそのインターフェイスを実装することをお勧めします。If you must test for value equality, the recommended way is to also apply the where T : IEquatable<T> or where T : IComparable<T> constraint and implement the interface in any class that will be used to construct the generic class.

複数のパラメーターを制約するConstraining multiple parameters

複数のパラメーターに制約を適用できます。また、複数の制約を 1 つのパラメーターに適用することができます。次に例を示します。You can apply constraints to multiple parameters, and multiple constraints to a single parameter, as shown in the following example:

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

非バインド型パラメーターUnbounded type parameters

パブリック クラス SampleClass<T>{} の T など、制約がない型パラメーターは、非バインド型パラメーターと呼ばれます。Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. 非バインド型パラメーターには次の規則があります。Unbounded type parameters have the following rules:

  • != および == 演算子は使用できません。これは、具象型引数がこれらの演算子をサポートするという保証がないためです。The != and == operators cannot be used because there is no guarantee that the concrete type argument will support these operators.
  • これらの演算子は System.Object との間で相互に変換できます。また、任意のインターフェイス型に明示的に変換できます。They can be converted to and from System.Object or explicitly converted to any interface type.
  • これらは null と比較することができます。You can compare them to null. 非バインド型パラメーターと null を比較し、その型引数が値の型の場合、比較結果として常に false が返されます。If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

制約としての型パラメーターType parameters as constraints

制約としてジェネリック型パラメーターを使用する方法は、独自の型パラメーターがあるメンバー関数が、含まれる型の型パラメーターにそのパラメーターを制約する必要がある場合に便利です。次に例を示します。The use of a generic type parameter as a constraint is useful when a member function with its own type parameter has to constrain that parameter to the type parameter of the containing type, as shown in the following example:

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

前の例の T は、Add メソッドのコンテキストでは型の制約ですが、List クラスのコンテキストでは非バインド型パラメーターです。In the previous example, T is a type constraint in the context of the Add method, and an unbounded type parameter in the context of the List class.

型パラメーターは、ジェネリック クラス定義の制約としても使用できます。Type parameters can also be used as constraints in generic class definitions. 型パラメーターは、他の型パラメーターと共に山かっこ内で宣言する必要があります。The type parameter must be declared within the angle brackets together with any other type parameters:

//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }

ジェネリック クラスで制約として型パラメーターを使用する方法が便利なのは、限られた場合のみです。コンパイラでは、System.Object から派生したことを除き、型パラメーターに関して何も仮定できないためです。The usefulness of type parameters as constraints with generic classes is limited because the compiler can assume nothing about the type parameter except that it derives from System.Object. 2 つの型パラメーター間に継承関係を適用するシナリオには、ジェネリック クラスの制約として型パラメーターを使用してください。Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

アンマネージド制約Unmanaged constraint

C# 7.3 以降、unmanaged 制約を指定して、型パラメーターが アンマネージド制約である必要があることを指定できます。Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be an unmanaged type. アンマネージド型は参照型ではない型であり、任意の入れ子のレベルに参照型フィールドを含みません。An unmanaged type is a type that is not a reference type and doesn't contain reference type fields at any level of nesting. unmanaged 制約では、次の例のように、メモリのブロックとして操作できる型を処理する再利用可能なルーチンを記述できます。The unmanaged constraint enables you to write reusable routines to work with types that can be manipulated as blocks of memory, as shown in the following example:

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

ビルトイン型ではない型で sizeof 演算子を使用するため、先行するメソッドは unsafe コンテキストでコンパイルされる必要があります。The preceding method must be compiled in an unsafe context because it uses the sizeof operator on a type not known to be a built-in type. unmanaged 制約なしで、sizeof 演算子を使用することはできません。Without the unmanaged constraint, the sizeof operator is unavailable.

制約をデリゲートするDelegate constraints

また、C# 7.3 以降、基底クラスの制約として System.Delegate または System.MulticastDelegate を使用することもできます。Also beginning with C# 7.3, you can use System.Delegate or System.MulticastDelegate as a base class constraint. CLR では常にこの制約を許可していますが、C# 言語では許可されていません。The CLR always allowed this constraint, but the C# language disallowed it. System.Delegate 制約では、タイプ セーフな方法でデリゲートを処理するコードを記述できます。The System.Delegate constraint enables you to write code that works with delegates in a type-safe manner. 次のコードでは、同じ型であることを提供する 2 つのデリゲートを組み合わせる拡張メソッドを定義します。The following code defines an extension method that combines two delegates provided they are the same type:

public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate
    => Delegate.Combine(source, target) as TDelegate;

上述のメソッドを使用して、同じ型のデリゲートを組み合わせることができます。You can use the above method to combine delegates that are the same type:

Action first = () => Console.WriteLine("this");
Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);
combined();

Func<bool> test = () => true;
// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

最後の行のコメントを解除した場合、コンパイルされません。If you uncomment the last line, it won't compile. firsttest の両方が型をデリゲートしますが、これらは異なるデリゲート型です。Both first and test are delegate types, but they are different delegate types.

列挙の制約Enum constraints

C# 7.3 以降、基底クラスの制約として System.Enum 型を指定することもできます。Beginning in C# 7.3, you can also specify the System.Enum type as a base class constraint. CLR では常にこの制約を許可していますが、C# 言語では許可されていません。The CLR always allowed this constraint, but the C# language disallowed it. System.Enum を使用するジェネリックは、System.Enum の静的メソッドの使用から結果をキャッシュするために、タイプ セーフのプログラミングを提供します。Generics using System.Enum provide type-safe programming to cache results from using the static methods in System.Enum. 次の例では、列挙型の有効な値をすべて見つけて、それらの値をその文字列表記にマップするディクショナリをビルドします。The following sample finds all the valid values for an enum type, and then builds a dictionary that maps those values to its string representation.

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

使用されるメソッドではリフレクションを使用します。これは、パフォーマンスに影響があります。The methods used make use of reflection, which has performance implications. このメソッドを呼び出して、リフレクションを必要とする呼び出しを繰り返すことなく、キャッシュおよび再利用されるコレクションをビルドできます。You can call this method to build a collection that is cached and reused rather than repeating the calls that require reflection.

次の例で示すように、このメソッドを使用して、列挙を作成し、その値と名前のディクショナリをビルドできます。You could use it as shown in the following sample to create an enum and build a dictionary of its values and names:

enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)
    Console.WriteLine($"{pair.Key}:\t{pair.Value}");

参照See Also