Generics
Generics let you write type-safe code that works with any data type, without sacrificing performance or safety. They eliminate the need for casting and boxing.
Generic Class
public class Repository<T> where T : class, new()
{
private readonly List<T> items = new();
public void Add(T item) => items.Add(item);
public T? GetById(int index)
{
if (index < 0 || index >= items.Count)
return null;
return items[index];
}
public IReadOnlyList<T> GetAll() => items.AsReadOnly();
public int Count => items.Count;
}
// Usage
var repo = new Repository<Product>();
repo.Add(new Product { Name = "Widget" });
repo.Add(new Product { Name = "Gadget" });
Product? item = repo.GetById(0);
Console.WriteLine(item?.Name); // Widget
Generic Methods
public static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) >= 0 ? a : b;
}
Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "banana")); // banana
Constraints
Constraints restrict what types can be used with a generic:
where T : struct // value type only
where T : class // reference type only
where T : new() // must have parameterless constructor
where T : IComparable<T> // must implement interface
where T : BaseClass // must derive from class
where T : notnull // cannot be null
Built-in Generic Collections
You use generics every day in C#:
List<string> names = new();
Dictionary<string, int> scores = new();
Queue<Task> tasks = new();
Stack<int> history = new();
HashSet<string> unique = new();
Why Not Use object?
Before generics, collections stored object, requiring casts and boxing:
// Old way — boxing, no type safety
ArrayList list = new();
list.Add(42); // boxed to object
int n = (int)list[0]; // must cast back
// Generic way — type safe, no boxing
List<int> list = new() { 42 };
int n = list[0]; // no cast needed