Fundamentals – Part 10

10/02/2025
3 minute read

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 abstract members 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() =&gt; _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 abstract opens 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.

An error has occurred. This application may no longer respond until reloaded. Reload x