SkillByAIOpen interactive version →

Lesson 10 / 25

Generics and Constraints

Write type-safe reusable code with generic types, methods and constraints.

One implementation, many types

Generics let you write a type or method once with a type parameter, such as List<T> or Dictionary<TKey, TValue>, and use it with any type while keeping full compile-time type checking and avoiding casts. Unlike Java's type erasure, .NET generics are reified: the runtime knows List<int> and List<string> are different types, and generic code over value types is specialised, so List<int> stores ints without boxing. Constraints with where restrict what T can be, and in return let you use its members: where T : class, where T : struct, where T : notnull, where T : new() (has a parameterless constructor), where T : SomeBase, where T : IComparable<T>. Generic math, through interfaces such as INumber<T>, lets one method work for int, double and decimal. Variance (out T in IEnumerable<out T>) allows an IEnumerable<string> to be used as an IEnumerable<object>.

A template with a slot for a type

A generic type is a mould with a type-shaped slot; each use fills the slot with a concrete type.

Figure 4.1 — One generic definition, several constructed types.

Generic methods with constraints

The constraint makes CompareTo and arithmetic available on T.

using System.Numerics;

T MaxOf<T>(IEnumerable<T> items) where T : IComparable<T>
{
    using var e = items.GetEnumerator();
    if (!e.MoveNext()) throw new ArgumentException("Empty sequence", nameof(items));
    T max = e.Current;
    while (e.MoveNext())
        if (e.Current.CompareTo(max) > 0) max = e.Current;
    return max;
}

T SumOf<T>(IEnumerable<T> items) where T : INumber<T>
{
    T total = T.Zero;
    foreach (var x in items) total += x;
    return total;
}

Console.WriteLine(MaxOf(new[] { "pear", "apple", "mango" }));
Console.WriteLine(SumOf(new[] { 1.5m, 2.25m }));

Avoid object and casting

Code that stores everything as object and casts back loses type safety and boxes value types. If you find yourself casting, a generic type parameter is usually the better design.

Quick check: How do .NET generics differ from Java generics?

  • .NET generics are erased at run time
  • .NET has no generic constraints
  • .NET generics are reified, so the runtime knows the type arguments and value types avoid boxing
  • .NET generics only work with classes
Answer

.NET generics are reified, so the runtime knows the type arguments and value types avoid boxing — .NET keeps generic type information at run time and specialises code for value types.