C#

Generics and constraints

Writing one method that works for any type, without losing the type back out.

After this lesson you can

  • Write a generic method with a type parameter
  • Constrain a type parameter so its members become usable
  • Explain what a generic method keeps that object-typed code loses

Without generics, a reusable method either repeats itself per type or falls back to object and loses the type on the way out.

public static object First(object[] items) => items[0];   // loses the type

A type parameter keeps the connection between what went in and what comes out:

public static T First<T>(List<T> items) => items[0];

int n = First(new List<int> { 1, 2, 3 });       // T inferred as int
string s = First(new List<string> { "a", "b" }); // T inferred as string

T is a placeholder the caller fills, almost always without writing it out — the compiler infers it from the argument.

Try it

One method, two different types, no castingcsharp-9
public static class Solution{    public static (T, T) Swap<T>(T a, T b) => (b, a);     public static string Describe()    {        var (a, b) = Swap(1, 2);        var (x, y) = Swap("left", "right");        return $"{a},{b} / {x},{y}";    }}
What to look for

Constraints

Inside the method, you may only do what every possible T allows — by default, almost nothing. where narrows what a caller may pass, and in exchange lets the method use it:

public static T Max<T>(T a, T b) where T : IComparable<T>
    => a.CompareTo(b) >= 0 ? a : b;

where T : IComparable<T> says: only types that know how to compare themselves to another instance of themselves may be used here. Without the constraint, a.CompareTo(b) would not compile — the compiler has no way to know an arbitrary T supports it.

Common constraints: where T : class (reference types only), where T : struct (value types only), where T : new() (must have a public parameterless constructor, so the method can create one), and where T : SomeBaseType (must derive from it, or implement it if it is an interface).

Generic classes work the same way

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

var intBox = new Box<int> { Value = 5 };
var stringBox = new Box<string> { Value = "hi" };

List<T> and Dictionary<TKey, TValue> are themselves ordinary generic classes, written with exactly this feature — nothing about them is special-cased into the language.

Try it yourself

2 visible tests · 2 hidden tests

Implement Max<T>(T a, T b) where T : IComparable<T>, returning whichever of the two is not smaller. Then implement MaxPairs(List<List<int>> intPairs, List<List<string>> stringPairs): for each [a, b] in intPairs, call Max and collect the result; do the same for stringPairs (string comparison is ordinal — the default CompareTo). Return a tuple (List<int>, List<string>) of the two result lists, in order.

  • MaxPairs([[3,7],[10,2]], [["apple","banana"],["zebra","ant"]])
  • MaxPairs([[1,1]], [])
Loading editor…

Sign up to check the hidden tests and save your progress. Sign up

Generics and constraints · C# · BuildStep