14.4. Generics#
You have been using generics since Chapter 7: List<T>, Dictionary<TKey, TValue>, Stack<T>. Here we look at what <T> actually means and how to write your own generic types and methods.
14.4.1. Why Generics Exist#
Without generics, a List that holds int would be a different class from one that holds string. Generics let one implementation work for any type:
// Non-generic — stores object, loses type safety
var list = new ArrayList();
list.Add(1);
list.Add("oops"); // compiles — bug at runtime
// Generic — type-safe at compile time
var nums = new List<int>();
nums.Add(1);
// nums.Add("oops"); // compile error
14.4.2. Generic Methods#
// A generic method works for any type T
static T[] Repeat<T>(T value, int count)
{
var arr = new T[count];
for (int i = 0; i < count; i++) arr[i] = value;
return arr;
}
int[] ints = Repeat(0, 5); // [0, 0, 0, 0, 0]
string[] strings = Repeat("hi", 3); // ["hi", "hi", "hi"]
Console.WriteLine(string.Join(", ", ints)); // 0, 0, 0, 0, 0
Console.WriteLine(string.Join(", ", strings)); // hi, hi, hi
14.4.3. Generic Classes#
// A minimal generic pair
class Pair<T1, T2>
{
public T1 First { get; }
public T2 Second { get; }
public Pair(T1 first, T2 second) { First = first; Second = second; }
public override string ToString() => $"({First}, {Second})";
}
var p1 = new Pair<string, int>("Alice", 30);
var p2 = new Pair<double, bool>(3.14, true);
Console.WriteLine(p1); // (Alice, 30)
Console.WriteLine(p2); // (3.14, True)
14.4.4. Type Constraints#
Use where T : ... to restrict what types T can be:
Constraint |
Meaning |
|---|---|
|
T must be a reference type |
|
T must be a value type |
|
T must have a parameterless constructor |
|
T must implement the interface |
|
T must inherit from |
// Constraint: T must implement IComparable<T> so we can compare
static T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "banana")); // banana
14.4.5. Generic Interfaces and LINQ#
All LINQ extension methods are generic — Where<T>, Select<T, TResult>, etc. You have been calling them without writing the type arguments because C# infers them:
// These two are identical:
nums.Where<int>(x => x > 0);
nums.Where(x => x > 0); // T inferred as int
Type inference makes generics feel like ordinary method calls at the use site.
14.4.6. Practice#
Write a generic
static T First<T>(List<T> list)that returns the first element or throwsInvalidOperationExceptionif the list is empty.Write a generic
static void Swap<T>(ref T a, ref T b)and test it withintandstring.Write a generic
Stack<T>class backed by aList<T>withPush,Pop, andPeekmethods. Add a constraint soTmust be non-nullable.
Footnotes