where (generic type constraint) (C# Reference)
The where
clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type. They declare capabilities that the type argument must have, and must be placed after any declared base class or implemented interfaces.
For example, you can declare a generic class, AGenericClass
, such that the type parameter T
implements the IComparable<T> interface:
public class AGenericClass<T> where T : IComparable<T> { }
Note
For more information on the where clause in a query expression, see where clause.
The where
clause can also include a base class constraint. The base class constraint states that a type to be used as a type argument for that generic type has the specified class as a base class, or is that base class. If the base class constraint is used, it must appear before any other constraints on that type parameter. Some types are disallowed as a base class constraint: Object, Array, and ValueType. The following example shows the types that can now be specified as a base class:
public class UsingEnum<T> where T : System.Enum { }
public class UsingDelegate<T> where T : System.Delegate { }
public class Multicaster<T> where T : System.MulticastDelegate { }
In a nullable context, the nullability of the base class type is enforced. If the base class is non-nullable (for example Base
), the type argument must be non-nullable. If the base class is nullable (for example Base?
), the type argument can be either a nullable or non-nullable reference type. The compiler issues a warning if the type argument is a nullable reference type when the base class is non-nullable.
The where
clause can specify that the type is a class
or a struct
. The struct
constraint removes the need to specify a base class constraint of System.ValueType
. The System.ValueType
type can't be used as a base class constraint. The following example shows both the class
and struct
constraints:
class MyClass<T, U>
where T : class
where U : struct
{ }
In a nullable context, the class
constraint requires a type to be a non-nullable reference type. To allow nullable reference types, use the class?
constraint, which allows both nullable and non-nullable reference types.
The where
clause can include the notnull
constraint. The notnull
constraint limits the type parameter to non-nullable types. The type can be a value type or a non-nullable reference type. The notnull
constraint is available for code compiled in a nullable enable
context. Unlike other constraints, if a type argument violates the notnull
constraint, the compiler generates a warning instead of an error. Warnings are only generated in a nullable enable
context.
The addition of nullable reference types introduces a potential ambiguity in the meaning of T?
in generic methods. If T
is a struct
, T?
is the same as System.Nullable<T>. However, if T
is a reference type, T?
means that null
is a valid value. The ambiguity arises because overriding methods can't include constraints. The new default
constraint resolves this ambiguity. You add it when a base class or interface declares two overloads of a method, one that specifies the struct
constraint, and one that doesn't have either the struct
or class
constraint applied:
public abstract class B
{
public void M<T>(T? item) where T : struct { }
public abstract void M<T>(T? item);
}
You use the default
constraint to specify that your derived class overrides the method without the constraint in your derived class, or explicit interface implementation. It's only valid on methods that override base methods, or explicit interface implementations:
public class D : B
{
// Without the "default" constraint, the compiler tries to override the first method in B
public override void M<T>(T? item) where T : default { }
}
Important
Generic declarations that include the notnull
constraint can be used in a nullable oblivious context, but compiler does not enforce the constraint.
#nullable enable
class NotNullContainer<T>
where T : notnull
{
}
#nullable restore
The where
clause can also include an unmanaged
constraint. The unmanaged
constraint limits the type parameter to types known as unmanaged types. The unmanaged
constraint makes it easier to write low-level interop code in C#. This constraint enables reusable routines across all unmanaged types. The unmanaged
constraint can't be combined with the class
or struct
constraint. The unmanaged
constraint enforces that the type must be a struct
:
class UnManagedWrapper<T>
where T : unmanaged
{ }
The where
clause can also include a constructor constraint, new()
. That constraint makes it possible to create an instance of a type parameter using the new
operator. The new() Constraint lets the compiler know that any type argument supplied must have an accessible parameterless constructor. For example:
public class MyGenericClass<T> where T : IComparable<T>, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
The new()
constraint appears last in the where
clause, unless it's followed by the allows ref struct
anti-constraint. The new()
constraint can't be combined with the struct
or unmanaged
constraints. All types satisfying those constraints must have an accessible parameterless constructor, making the new()
constraint redundant.
This anti-constraint declares that the type argument for T
can be a ref struct
type. For example:
public class GenericRefStruct<T> where T : allows ref struct
{
// Scoped is allowed because T might be a ref struct
public void M(scoped T parm)
{
}
}
The generic type or method must obey ref safety rules for any instance of T
because it might be a ref struct
. The allows ref struct
clause can't be combined with the class
or class?
constraint. The allows ref struct
anti-constraint must follow all constraints for that type argument.
With multiple type parameters, use one where
clause for each type parameter, for example:
public interface IMyInterface { }
namespace CodeExample
{
class Dictionary<TKey, TVal>
where TKey : IComparable<TKey>
where TVal : IMyInterface
{
public void Add(TKey key, TVal val) { }
}
}
You can also attach constraints to type parameters of generic methods, as shown in the following example:
public void MyMethod<T>(T t) where T : IMyInterface { }
Notice that the syntax to describe type parameter constraints on delegates is the same as that of methods:
delegate T MyDelegate<T>() where T : new();
For information on generic delegates, see Generic Delegates.
For details on the syntax and use of constraints, see Constraints on Type Parameters.
C# language specification
For more information, see the C# Language Specification. The language specification is the definitive source for C# syntax and usage.