Intermediate · Collections, generics and LINQ · topic 23 of 43 · 30 min
Generics: reusable, type-safe code
Explanation
The problem generics solve
Before generics (C# 2.0), collections stored everything as object. You could put an int and a string into the same ArrayList, and you had to cast every item back, which could fail at run time. Value types were boxed, which was slow. Generics fixed this: List<int> holds only ints, needs no casts and does not box.
var old = new System.Collections.ArrayList();
old.Add(10);
old.Add("ten"); // allowed, sadly
int first = (int)old[0]!; // cast needed
// int second = (int)old[1]!; // compiles, but InvalidCastException at run time
var modern = new List<int> { 10 };
// modern.Add("ten"); // compile error: caught early
int a = modern[0]; // no castGeneric methods
static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y); // T is inferred as int
string p = "Pune", q = "Goa";
Swap(ref p, ref q); // T is string
Console.WriteLine($"{x} {y} {p} {q}"); // 2 1 Goa PuneGeneric classes
Result<decimal> fare = Result<decimal>.Ok(1250m);
Result<string> pnr = Result<string>.Fail("PNR not found");
Console.WriteLine(fare.Success ? $"Fare {fare.Value}" : fare.Error);
Console.WriteLine(pnr.Success ? pnr.Value : pnr.Error);
public class Result<T>
{
public bool Success { get; }
public T? Value { get; }
public string? Error { get; }
private Result(bool success, T? value, string? error)
{
Success = success; Value = value; Error = error;
}
public static Result<T> Ok(T value) => new(true, value, null);
public static Result<T> Fail(string error) => new(false, default, error);
}Constraints: telling the compiler what T can do
Inside a generic method, the compiler only knows that T is "some type". You cannot use > on it, call a method on it, or create it with new, unless you add a constraint with where.
static T Largest<T>(IEnumerable<T> items) where T : IComparable<T>
{
T best = items.First();
foreach (T item in items)
if (item.CompareTo(best) > 0) best = item;
return best;
}
Console.WriteLine(Largest(new[] { 4, 19, 7 })); // 19
Console.WriteLine(Largest(new[] { "mango", "apple", "kiwi" })); // mango| Constraint | Meaning |
|---|---|
| where T : class | T must be a reference type |
| where T : struct | T must be a non-nullable value type |
| where T : notnull | T must not be a nullable type |
| where T : new() | T must have a public parameterless constructor, so you can write new T() |
| where T : SomeBaseClass | T must be that class or derive from it |
| where T : ISomeInterface | T must implement that interface |
Generic interfaces: a repository
public interface IRepository<T> where T : class
{
void Add(T item);
T? FindById(int id);
IReadOnlyList<T> All();
}Common mistakes and fixes
- Using operators like > or + on T: error CS0019. Use IComparable<T> (or, in modern .NET, generic math interfaces such as INumber<T>, which are more advanced).
- Writing new T() without where T : new(): error. Add the constraint.
- Making everything generic: if a method only ever works with Order, use Order. Generics are for code that truly works with many types.
- Returning null from a method returning T without a constraint: use default, or constrain T to class and return T?.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Generics: reusable, type-safe code · 1/7
In this lesson you will write code that works with any type, safely: generics.
Prefer a video by a teacher? Find free videos on “C# generics tutorial generic methods classes constraints where T” on YouTube ↗
In real life: A paged API response for a product catalogue
Shopping apps show products page by page. The API returns the same wrapper for products, reviews or orders: the items on this page, the page number and the total count. One generic class serves every list in the app.
var page = Paginate(new[] { "Kurta", "Saree", "Dupatta", "Lehenga", "Sherwani" }, pageNo: 2, size: 2);
Console.WriteLine($"Page {page.PageNo} of {page.TotalPages}: {string.Join(", ", page.Items)}");
static PagedResult<T> Paginate<T>(IReadOnlyList<T> all, int pageNo, int size)
{
var items = new List<T>();
for (int i = (pageNo - 1) * size; i < Math.Min(pageNo * size, all.Count); i++)
items.Add(all[i]);
return new PagedResult<T>(items, pageNo, (all.Count + size - 1) / size);
}
public record PagedResult<T>(IReadOnlyList<T> Items, int PageNo, int TotalPages);Practice (0/4 done)
What is the main benefit of List<int> over the old ArrayList?
static T First<T>(T[] arr) => arr[0]; What is the type of First(new[] { 2.5, 3.5 })?
Which constraint lets you write new T() inside a generic method?
Write a generic class Cache<TKey, TValue> (TKey must be notnull) that wraps a Dictionary and has: Set(key, value), TryGet(key, out TValue value) and a Count property. Use it once with <string, decimal> for product prices and once with <int, string> for roll numbers to names.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- LINQ basics: query data in a few linesCollections, generics and LINQ · same module
- LINQ in depth: grouping, joins, deferred execution, IQueryableCollections, generics and LINQ · same module
- Generics: type parameters and constraintsGo Programming (backend and cloud) · Packages, testing and generics
- GenericsJavaScript Programming (language in depth) · TypeScript basics
- GenericsJava Programming (Core to Spring Boot) · Exceptions and generics
- Generics and utility typesFrontend Web Development · TypeScript basics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
In List<T>, what is T?
Answer: A type parameter (placeholder for a type)
T is filled in with a real type when you use the list.
static bool Bigger<T>(T a, T b) => a > b; Why does this not compile?
Answer: The compiler does not know T supports >
Add a constraint such as IComparable<T> and use CompareTo.
What does default give for T = int and for T = string?
Answer: 0 and null
default is 0 for int and null for reference types.
Which is a generic type in .NET?
Answer: Dictionary<TKey,TValue>
Dictionary has two type parameters.
where T : class means...
Answer: T must be a reference type
class here means any reference type.
In which Go version did generics arrive?Related
Answer: 1.18
Go 1.18 (March 2022) added type parameters.
Which built-in type is generic?Related
What do generics mainly give you?Related
Answer: Type safety at compile time without casts
Errors move from run time to compile time.
From Generics
Which utility type makes every property optional, for an "edit profile" form where any field may change?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Collections, generics and LINQ” before its exam: LINQ basics: query data in a few lines, LINQ in depth: grouping, joins, deferred execution, IQueryable.
- Module 6 exam: Collections, generics and LINQ · 14 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Exceptions, delegates, events and async. 43 topics left in the course before the final exam.
← Previous: Collections: List, Dictionary, HashSet, Queue and Stack