Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Generic constraints are compile-time rules applied to generic type parameters that restrict the specific types a developer can substitute for those parameters. By enforcing these boundaries using the where contextual keyword, the compiler can safely assume the availability of specific members (such as methods, properties, or constructors) and memory allocation behaviors associated with the constrained type.

Syntax Visualization

Constraints are declared using the where keyword, placed after the generic type parameter list and base class/interface declarations.
// Constraint on a generic class
public class GenericClass<T> where T : constraint_type 
{ 
}

// Constraint on a generic method
public void GenericMethod<T>(T parameter) where T : constraint_type 
{ 
}

Types of Constraints

C# provides several categories of constraints to enforce type safety and memory layout rules.

1. Type Category Constraints

These restrict the fundamental memory allocation or nullability characteristics of the type parameter.
  • where T : struct: Restricts T to a non-nullable value type. Because all value types have an implicit parameterless constructor, the struct constraint implies the new() constraint. Note: Explicitly combining struct and new() (e.g., where T : struct, new()) is forbidden and results in a compiler error (CS0451).
  • where T : class: Restricts T to a non-nullable reference type (class, interface, delegate, or array).
  • where T : class?: Restricts T to a nullable or non-nullable reference type.
  • where T : notnull: Restricts T to a non-nullable value type or non-nullable reference type.
  • where T : unmanaged: Restricts T to an unmanaged type. An unmanaged type is a non-reference type that contains no reference type fields at any level of nesting (e.g., primitives, enums, or structs containing only unmanaged types).
  • where T : allows ref struct: (Introduced in C# 13) An “anti-constraint” that expands the allowed types to include ref struct types (like Span<T>), which are normally forbidden in generics.

2. Inheritance and Implementation Constraints

These restrict T based on the type hierarchy.
  • where T : <BaseClassName>: Enforces that T must be the specified base class or derive from it.
  • where T : <InterfaceName>: Enforces that T must implement the specified interface. You can specify multiple interfaces.
  • where T : U: Known as a naked type constraint. Enforces that the type argument supplied for T must be or derive from the type argument supplied for U.

3. Constructor Constraints

  • where T : new(): Enforces that T must have a public, parameterless constructor. When used with other constraints, new() must be the last constraint specified.

Combining Constraints

You can apply multiple constraints to a single type parameter, and you can constrain multiple type parameters independently. When combining constraints on a single type parameter, the compiler enforces a strict ordering sequence:
  1. Primary Constraint: Must be first. Can be class, class?, struct, unmanaged, notnull, or a specific base class. (You can only have one primary constraint).
  2. Secondary Constraints: Interfaces or naked type constraints (U).
  3. Constructor Constraint: The new() constraint must be last.
// Multiple constraints on a single type parameter (T)
// Order: Primary (BaseClass), Secondary (IInterface), Constructor (new())
public class DataProcessor<T> where T : BaseClass, IInterface, new()
{
}

// Constraints on multiple type parameters (T and U)
public class DictionaryMapper<T, U> 
    where T : notnull 
    where U : class, IDisposable, new()
{
}

// Naked type constraint (T must inherit from U)
public class TypeRelator<T, U> where T : U
{
}

Inheritance of Constraints

When defining a derived generic class, generic classes do not implicitly inherit constraints from a generic base class. You must explicitly repeat the constraints on the type parameters that are passed to the base class. For generic methods, constraint inheritance depends strictly on the type of implementation:
  • Method Overrides and Explicit Interface Implementations: Constraints are implicitly inherited from the base declaration. The compiler forbids explicitly repeating the constraints.
  • Implicit Interface Implementations: Constraints must be explicitly repeated to match the interface definition. Failure to do so results in compiler error CS0425.
public class Base<T> where T : class 
{ 
}

// Class-level: Constraints MUST be explicitly repeated on the derived class
public class Derived<T> : Base<T> where T : class 
{ 
}

public interface IProcessor
{
    void ImplicitProcess<T>() where T : struct;
    void ExplicitProcess<T>() where T : struct;
}

public abstract class MethodBase
{
    public abstract void OverrideProcess<T>() where T : struct;
}

public class MethodDerived : MethodBase, IProcessor
{
    // Method Override: Constraints are IMPLICITLY inherited and cannot be repeated
    public override void OverrideProcess<T>() 
    { 
    }

    // Explicit Interface Implementation: Constraints are IMPLICITLY inherited
    void IProcessor.ExplicitProcess<T>()
    {
    }

    // Implicit Interface Implementation: Constraints MUST be explicitly repeated
    public void ImplicitProcess<T>() where T : struct
    {
    }
}
Master C# with Deep Grasping Methodology!Learn More