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
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}"; }}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 testsImplement 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]], [])
Sign up to check the hidden tests and save your progress. Sign up