🌍 Why Do We Need Generics?
Imagine you want to create a stack (like a pile of books 📚):
-
You can push items on top
-
You can pop items off the top
If we write a stack for integers:
public class IntStack
{
private int[] items = new int[10];
private int index = 0;
public void Push(int item) => items[index++] = item;
public int Pop() => items[--index];
}
👉 Problem: This only works for int.
What if we want a string stack? Or a Customer stack?
We’d have to duplicate code for every type. 😢
✅ Solution: Generics
Generics let us create type-safe reusable code without duplication.
We can say: “I don’t care what type it is yet — I’ll decide later.”
1) Generic Classes
Here’s a generic stack:
// Generic class "Stack<T>"
// The <T> is a placeholder for any type
public class Stack<T>
{
private T[] items = new T[10]; // Array of type T
private int index = 0;
// Push adds an item of type T
public void Push(T item) => items[index++] = item;
// Pop returns an item of type T
public T Pop() => items[--index];
}
Usage:
var intStack = new Stack<int>(); // Stack of integers
intStack.Push(100);
intStack.Push(200);
Console.WriteLine(intStack.Pop()); // 200
var stringStack = new Stack<string>(); // Stack of strings
stringStack.Push("Hello");
stringStack.Push("World");
Console.WriteLine(stringStack.Pop()); // World
👉 Why is this better?
-
No duplication → One class for all types
-
Type-safe → Can’t push a
stringintoStack<int> -
Faster → No boxing/unboxing
2) Generic Methods
Sometimes, you don’t need a whole generic class — just a method.
Example: Swap Two Values
public static void Swap<T>(ref T a, ref T b)
{
// Temporary variable of type T
T temp = a;
a = b;
b = temp;
}
Usage:
int x = 5, y = 10;
Swap(ref x, ref y);
Console.WriteLine($"x={x}, y={y}"); // x=10, y=5
string s1 = "A", s2 = "B";
Swap(ref s1, ref s2);
Console.WriteLine($"{s1}, {s2}"); // B, A
👉 Why ref?
So we can swap the original variables, not just local copies.
👉 Why generic?
Because now we don’t need to write separate swap methods for int, string, double, etc.
Example: Generic Maximum Finder
public static T Max<T>(T a, T b) where T : IComparable<T>
{
// CompareTo() comes from IComparable<T> interface
return a.CompareTo(b) > 0 ? a : b;
}
Console.WriteLine(Max(10, 20)); // 20
Console.WriteLine(Max("Apple", "Pear")); // Pear
👉 Why where T : IComparable<T>?
Because not every type supports comparison.
This constraint makes sure only comparable types are allowed.
3) Constraints in Generics
Constraints restrict what types can be used with generics.
Types of Constraints:
-
where T : struct→ Value types only (int,double,DateTime) -
where T : class→ Reference types only (string, custom classes) -
where T : new()→ Must have a public parameterless constructor -
where T : BaseClass→ Must inherit from a specific class -
where T : IInterface→ Must implement an interface
Example: Repository Pattern (Simplified)
// All entities in system must have an Id
public interface IEntity
{
int Id { get; set; }
}
// Generic repository with constraint
public class Repository<T> where T : class, IEntity, new()
{
private List<T> items = new();
public void Add(T item) => items.Add(item);
public T GetById(int id) => items.FirstOrDefault(i => i.Id == id);
}
Usage:
public class Customer : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
var repo = new Repository<Customer>();
repo.Add(new Customer { Id = 1, Name = "Alice" });
var customer = repo.GetById(1);
Console.WriteLine(customer.Name); // Alice
👉 Why constraints here?
Because we need an Id property to look up items.
Without where T : IEntity, we’d risk calling .Id on something that doesn’t have it.
4) Multiple Type Parameters
Sometimes one placeholder (T) isn’t enough.
We can use multiple: <TKey, TValue>.
Example: Pair (like Key-Value)
public class Pair<TKey, TValue>
{
public TKey Key { get; set; }
public TValue Value { get; set; }
public Pair(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
Usage:
var p1 = new Pair<int, string>(1, "One");
Console.WriteLine($"{p1.Key} - {p1.Value}"); // 1 - One
var p2 = new Pair<string, bool>("IsActive", true);
Console.WriteLine($"{p2.Key} - {p2.Value}"); // IsActive - True
👉 This is exactly how Dictionary<TKey, TValue> works internally.
5) Generic Interfaces
Interfaces can also be generic.
This makes it possible to create reusable contracts.
Example: Generic Repository Interface
public interface IRepository<T>
{
void Add(T item);
IEnumerable<T> GetAll();
}
// In-memory implementation
public class InMemoryRepository<T> : IRepository<T>
{
private List<T> items = new();
public void Add(T item) => items.Add(item);
public IEnumerable<T> GetAll() => items;
}
Usage:
var repo = new InMemoryRepository<string>();
repo.Add("Hello");
repo.Add("World");
foreach (var item in repo.GetAll())
Console.WriteLine(item);
👉 Why?
Because now you can create repositories for Customer, Order, Product, etc., with one interface.
6) Real-World Mini Example: Generic Logger
A logger records messages. Instead of writing separate loggers for every class, make one generic logger.
public class Logger<T>
{
public void Log(string message)
{
// typeof(T).Name gives the class name
Console.WriteLine($"[{typeof(T).Name}] {message}");
}
}
Usage:
var customerLogger = new Logger<Customer>();
customerLogger.Log("Customer created."); // [Customer] Customer created.
var orderLogger = new Logger<Order>();
orderLogger.Log("Order placed."); // [Order] Order placed.
👉 Benefit:
Logs are automatically tagged with the type name, so you know where they came from.
🎯 Key Takeaways
-
Generics let you write reusable, type-safe code.
-
<T>is a placeholder for a type (decided when using the class/method). -
Generic Methods → Reusable algorithms (
Swap,Max). -
Generic Classes → Reusable structures (
Stack<T>,Pair<TKey, TValue>). -
Constraints → Keep generics safe (
where T : class, IEntity). -
Multiple Type Parameters → Support advanced structures like dictionaries.
-
Generic Interfaces → Define reusable contracts.
-
Real-world examples → Repository, Logger, API responses.
✅ With this, you have a solid foundation in generics.
Link for part 2👇
In 🔗 Part 2, we explored delegates, events, factories, strategies.
Comments
Post a Comment