Skip to main content

Basic Syntax

Declaration

Generic Class

public class Box<T>
{
public T Value { get; set; }
}

Generic Method

public T Method<T>(T value)
{
return value;
}

Generic Interface

public interface IRepository<T>
{
T Get(int id);
}

Generic Constraints

public class Factory<T> where T : new()
{
public T Create() => new T();
}
tip
  • T is a type parameter (a placeholder)
  • where T : new() is a constraint, which restricts what kinds of types can be used

Constraint

Generic constraints specify the requirements that a type parameter must satisfy. They allow the compiler to ensure that T supports certain operations, such as having a parameterless constructor, inheriting from a specific base class, or implementing an interface.

Without constraints, the compiler cannot assume anything about T.

Basic Syntax

class MyClass<T> where T : ConstraintType
{
}

Multiple constraints can be combined:

class MyClass<T> where T : BaseClass, IInterface, new()
{
}

Constraint order must follow this pattern:

  1. class or struct
  2. Base class
  3. Interfaces
  4. new()

Common Constraint Types

Reference type constraint

where T : class

Value type constraint

where T : struct

Parameterless constructor

where T : new()

Inherit from a base class

where T : BaseClass

Implement an interface

where T : IDisposable

Why Constraints Are Needed

Calling members that may not exist

void CloseItem<T>(T item)
{
item.Dispose(); // Cannot compile
}
void CloseItem<T>(T item) where T : IDisposable
{
item.Dispose(); // ✔ Safe
}

Creating instances of T

class Factory<T>
{
T Create() => new T(); // Not allowed
}
class Factory<T> where T : new()
{
T Create() => new T(); // ✔ Allowed
}

Method-Level Constraints

Constraints can also be applied to individual methods:

public void Save<T>(T item) where T : IEntity
{
item.Store();
}