Lesson 3 of 45 intermediate

Generics and Constraints

One class that works for many types, and the compiler still checks every use

Guess first

Pick an answer. You will see if you were right at the end of the lesson.

What does the constraint where T : class, IEntity let you do inside the class?

Open interactive version (quiz + challenge)

Real-world analogy

Think of a shipping container with a label on the door. A plain box that holds anything forces you to open it and guess what is inside. A labelled container says Bananas, and the port refuses to load shoes into it. Here the container is a generic class, the label is the type argument T, and the port is the compiler.

What is it?

Generics let you write a class or method with a placeholder type, written T. It is filled in when you use it. The compiler checks every use, so you get safety without casts, and value types avoid boxing. Constraints tell the compiler what T must be able to do, which is what lets you call members on it.

Real-world relevance

A service layer returns Result<Order> instead of throwing for expected failures like "not found". The same Repository<T> serves customers, products and invoices, and the constraint IEntity guarantees each has an Id. Old code that still uses ArrayList is a common source of invalid-cast crashes.

Key points

Quick check

Answer from memory — scrolling up first makes it stick less.

Why is List<string> not assignable to List<object>?

What is the main cost of using a non-generic ArrayList with ints?

Code example

public interface IEntity
{
    int Id { get; }
}

public class Result<T>
{
    public T? Value { get; }
    public string? Error { get; }
    public bool IsSuccess => Error is null;

    private Result(T? value, string? error)
    {
        Value = value;
        Error = error;
    }

    public static Result<T> Ok(T value) => new(value, null);
    public static Result<T> Fail(string error) => new(default, error);
}

public class Repository<T> where T : class, IEntity
{
    private readonly List<T> _items = new();

    public void Add(T item) => _items.Add(item);

    public Result<T> Find(int id)
    {
        var found = _items.FirstOrDefault(x => x.Id == id);
        return found is null ? Result<T>.Fail("Not found") : Result<T>.Ok(found);
    }
}

Line-by-line walkthrough

  1. 1. IEntity promises that anything stored has an Id. The repository relies on this.
  2. 2. Result of T holds either a Value or an Error. The constructor is private, so only Ok and Fail can create one.
  3. 3. Ok and Fail are the two doors. IsSuccess is true when Error is null.
  4. 4. Repository of T has a where clause: T must be a class and must implement IEntity.
  5. 5. Because of that constraint, Find can write x.Id inside the lambda. Without it the compiler would reject the line.
  6. 6. Find returns a Result of T, so callers must look at IsSuccess before using Value.

Spot the bug

using System.Collections;

var ids = new ArrayList();
ids.Add(1);
ids.Add(2);
ids.Add("three");

int total = 0;
foreach (object id in ids)
{
    total += (int)id;
}
Need a hint?
What type does ArrayList promise for its items?
Show answer
ArrayList stores object, so each int is boxed and the compiler cannot stop the string "three". The cast <code>(int)id</code> then throws InvalidCastException at runtime. Fix: use <code>List&lt;int&gt;</code>. The bad Add will not compile, and no boxing happens.

Explain like I'm 5

A lunch box with a name on it only takes the food named on the label. You do not have to peek inside to know what you will get. One lunch box design works for many kinds of food.

Fun fact

Java generics are erased at runtime, so a List&lt;String&gt; becomes a plain list. In .NET the runtime keeps the real types, which is why generic collections of ints do not box.

Hands-on challenge

Say it out loud in 30 seconds: "Why do generics exist, and what does a constraint like where T : class do?" Then check: did you mention type safety without casts, no boxing, and calling members on T?

More resources

Open interactive version (quiz + challenge) Download the whole course as PDF / Word

Lock it in

← Back to course: .NET + React Playbook