Generics
Generics are one of the most powerful features in C#. They let you write reusable, type-safe code without sacrificing performance.
Instead of writing the same logic for int, string, or custom types, you write it once — and let the compiler generate the right code for each type.
In this post, I’ll cover:
- Why generics exist?
- Constraints.
- CRTP (Curiously Recurring Template Pattern).
static abstractmembers in interfaces (new in C# 11).
Why Generics?
Without generics, you’d either duplicate code or rely on object and type-casting:
public class Box
{
public object Value { get; set; }
}
var b = new Box ;
int n = (int)b.Value; // risk of runtime error
With generics
public class Box<T>
{
public T Value { get; set; }
}
var b = new Box<int> ;
int n = b.Value; // type-safe, no cast
Generics are resolved at compile time — so you get both performance (no boxing/unboxing) and safety.
Constrains
Constraints tell the compiler what operations are valid for a type parameter.
Example 1: where T : struct
public class Cache<T> where T : struct
{
public T? Item { get; set; }
}
Here T must be a value type (int, DateTime, etc.).
Example 2: where T : class
public class Repository<T> where T : class
{
public T? FindById(int id) => null;
}
Here T must be a reference type
Example 3: where T : new()
public static T Create<T>() where T : new()
{
return new T();
}
var x = Create<List<int>>(); // OK
// var y = Create<Stream>(); ❌ requires no-arg ctor
Example 4: where T : SomeBaseClass, ISomeInterface
public class Service<T> where T : Controller, IDisposable
{
public void DoWork(T controller) { ... }
}
You can stack multiple constraints.
CRTP (Curiously Recurring Template Pattern)
This pattern allows a base class to use the derived type as a type parameter. It’s common in C++ and works in C# too, Maybe I will extend on this in the future.
public abstract class Entity<TSelf>
where TSelf : Entity<TSelf>
{
public abstract TSelf Clone();
}
public class User : Entity<User>
{
public string Name { get; set; } = "";
public override User Clone() => new User ;
}
Now Clone always returns the correct type, not just Entity.
User u1 = new User ;
User u2 = u1.Clone(); // returns User, not base
This trick makes base classes more strongly typed.
Static Abstract Members in Interfaces (C# 11+)
Until recently, interfaces couldn’t declare static members. C# 11 introduced static abstract members, enabling generic math and similar scenarios.
public interface INumber<TSelf> where TSelf : INumber<TSelf>
{
static abstract TSelf operator +(TSelf left, TSelf right);
}
Implementations:
public readonly struct MyInt : INumber<MyInt>
{
private readonly int _value;
public MyInt(int v) => _value = v;
public static MyInt operator +(MyInt l, MyInt r)
=> new MyInt(l._value + r._value);
public override string ToString() => _value.ToString();
}
Usage in a generic method:
public static T Add<T>(T a, T b) where T : INumber<T>
=> a + b;
var sum = Add(new MyInt(2), new MyInt(3));
Console.WriteLine(sum); // 5
This enables generic algorithms over operators — something that wasn’t possible before without reflection or IL hacks.
Final Thoughts
- Generics give you type safety and performance.
- Constraints let you express exactly what kinds of types are valid.
- CRTP helps base classes return the right derived type.
static abstractopens the door for generic math and operator-based APIs.
Understanding these features gives you the ability to design more reusable, expressive, and future-proof APIs.


