Generics
Write classes and methods that work with any type while staying type-safe: generic methods, generic classes, constraints and why List<T> beats ArrayList.
List<string>, Dictionary<string, double>. The <T> is a type parameter: a placeholder that is filled in with a real type when the code is used. Generics let you write code once that works with many types, without losing type safety.
The problem generics solve
// Before generics: one method per type…static int LargerInt(int a, int b) => a > b ? a : b;static double LargerDouble(double a, double b) => a > b ? a : b; // …or 'object', which loses type safetyvar oldList = new System.Collections.ArrayList();oldList.Add(5);oldList.Add("five"); // compiles: anything goesint n = (int)oldList[1]!; // InvalidCastException at RUN timeGeneric methods
static T Larger<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) >= 0 ? a : b; static void Swap<T>(ref T a, ref T b) => (a, b) = (b, a); Console.WriteLine(Larger(7, 12)); // T inferred as intConsole.WriteLine(Larger("Beam", "Column")); // T = string int x = 1, y = 2;Swap(ref x, ref y);Console.WriteLine($"{x} {y}");12 Column 2 1
<int> in the call: the compiler infers T from the arguments.
Generic classes
public class Result<T>{ public bool Ok { get; } public T? Value { get; } public string Error { get; } = ""; private Result(bool ok, T? value, string error) { Ok = ok; Value = value; Error = error; } public static Result<T> Success(T value) => new(true, value, ""); public static Result<T> Fail(string error) => new(false, default, error);} Result<double> r = Result<double>.Success(42.2);Result<Beam> b = Result<Beam>.Fail("Profile not found");Console.WriteLine(r.Ok ? $"Value {r.Value}" : r.Error);Console.WriteLine(b.Ok ? "ok" : b.Error);Value 42.2 Profile not found
Constraints
| Constraint | T must… |
|---|---|
where T : class | |
where T : struct | |
where T : new() | new T()) |
where T : StructuralMember | |
where T : IComparable<T> | |
where T : notnull |
CompareTo).
default(T)
default gives the default value of whatever T is: 0 for numbers, false for bool, null for reference types. Handy for “not found” results in generic code.
Pair<TFirst, TSecond> with two properties and a method Pair<TSecond, TFirst> Swapped(). Create a Pair<string, int>("B1", 6), swap it and print both.
Show the solution
public class Pair<TFirst, TSecond>{ public TFirst First { get; } public TSecond Second { get; } public Pair(TFirst first, TSecond second) { First = first; Second = second; } public Pair<TSecond, TFirst> Swapped() => new(Second, First); public override string ToString() => $"({First}, {Second})";} var p = new Pair<string, int>("B1", 6);Console.WriteLine(p);Console.WriteLine(p.Swapped());(B1, 6) (6, B1)
Remember
Generics use type parameters ( <T>) to write reusable, type-safe code.The compiler usually infers Tfrom the arguments.Constraints ( where T : …) limit T and unlock members you can use.Generic collections avoid casts and boxing, so they are faster and safer.