Chapter 17: Generics
1. What are Generics? (Super Simple Analogy)
Imagine you’re building a storage box factory:
- Without generics → You make one box for integers, one for strings, one for dates… lots of duplicate factories!
- With generics → You make one smart box factory that can produce boxes for any type you want: Box<int>, Box<string>, Box<DateTime>, Box<Car> — all from one blueprint!
Generics = parameterized types You write code once, and it works perfectly with any type you give it.
2. Generic Classes – The Most Common Use
Syntax:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class GenericBox<T> // T is a placeholder (type parameter) { private T _item; public void Put(T item) { _item = item; } public T Get() { return _item; } } |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Create boxes for different types GenericBox<int> intBox = new GenericBox<int>(); intBox.Put(42); Console.WriteLine(intBox.Get()); // 42 GenericBox<string> stringBox = new GenericBox<string>(); stringBox.Put("Webliance from Hyderabad"); Console.WriteLine(stringBox.Get()); // Webliance from Hyderabad GenericBox<DateTime> dateBox = new GenericBox<DateTime>(); dateBox.Put(DateTime.Now); Console.WriteLine(dateBox.Get()); // Current date & time |
Type safety: If you try intBox.Put(“hello”) → compile error! That’s the magic — generics give you full type safety at compile time.
3. Generic Methods – Reuse Logic for Any Type
You can also make individual methods generic (even inside non-generic classes).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class Helper { // Generic method public static void Swap<T>(ref T a, ref T b) { T temp = a; a = b; b = temp; } // Generic method with multiple type parameters public static void PrintBoth<T1, T2>(T1 first, T2 second) { Console.WriteLine($"First: {first}, Second: {second}"); } } // Usage int x = 5, y = 10; Helper.Swap(ref x, ref y); Console.WriteLine($"x = {x}, y = {y}"); // x = 10, y = 5 Helper.PrintBoth("Webliance", 25); // First: Webliance, Second: 25 Helper.PrintBoth(DateTime.Now, true); // Works with any types! |
4. Generic Constraints – Control Which Types Are Allowed
Sometimes you need to restrict what types T can be.
Common constraints:
| Constraint | Meaning | Example Use Case |
|---|---|---|
| where T : class | T must be a reference type (class) | For objects that can be null |
| where T : struct | T must be a value type (struct, int, bool…) | Performance-critical code |
| where T : new() | T must have a parameterless constructor | Creating new instances of T |
| where T : SomeClass | T must inherit from SomeClass | Working with specific base classes |
| where T : ISomeInterface | T must implement ISomeInterface | Calling interface methods on T |
| where T : IComparable<T> | T must be comparable (can be sorted) | Sorting or finding max/min |
Real example with constraints:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Container<T> where T : class, new() // T must be class + have default constructor { public T CreateAndInitialize() { T item = new T(); // Allowed because of new() // Do something with item... return item; } } class Person { public string Name { get; set; } } var container = new Container<Person>(); Person p = container.CreateAndInitialize(); // Works! |
Another example – Sorting with IComparable
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class MaxFinder { public static T FindMax<T>(T[] items) where T : IComparable<T> { if (items == null || items.Length == 0) throw new ArgumentException("Array is empty!"); T max = items[0]; foreach (T item in items) { if (item.CompareTo(max) > 0) max = item; } return max; } } // Works with any comparable type int[] numbers = { 5, 2, 8, 1, 9 }; Console.WriteLine(MaxFinder.FindMax(numbers)); // 9 string[] names = { "Rahul", "Priya", "Amit", "Webliance" }; Console.WriteLine(MaxFinder.FindMax(names)); // "Webliance" (alphabetically last) |
5. Generic Collections – The Power in Real Life
The most common use of generics is in collections (from System.Collections.Generic):
| Generic Collection | Purpose | Example |
|---|---|---|
| List<T> | Dynamic array (grow/shrink) | List<string> names = new(); |
| Dictionary<TKey, TValue> | Key-value pairs (fast lookup) | Dictionary<int, string> users; |
| HashSet<T> | Unique items (no duplicates) | HashSet<int> ids; |
| Queue<T> / Stack<T> | FIFO / LIFO | Queue<string> tasks; |
| LinkedList<T> | Efficient insert/remove in middle | LinkedList<int> numbers; |
Example – Generic List in action
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
List<string> friends = new List<string>(); friends.Add("Rahul"); friends.Add("Priya"); friends.Add("Amit"); friends.Remove("Amit"); foreach (string friend in friends) { Console.WriteLine($"Hello, {friend}! 😊"); } Console.WriteLine($"Total friends: {friends.Count}"); |
Mini-Project: Generic Stack Machine
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
class GenericStack<T> { private List<T> _items = new List<T>(); public void Push(T item) { _items.Add(item); Console.WriteLine($"Pushed: {item}"); } public T Pop() { if (_items.Count == 0) throw new InvalidOperationException("Stack is empty!"); T item = _items[^1]; // Last element _items.RemoveAt(_items.Count - 1); Console.WriteLine($"Popped: {item}"); return item; } public T Peek() => _items[^1]; public bool IsEmpty => _items.Count == 0; } // Usage var intStack = new GenericStack<int>(); intStack.Push(10); intStack.Push(20); intStack.Push(30); Console.WriteLine($"Top: {intStack.Peek()}"); // 30 intStack.Pop(); // Removes 30 Console.WriteLine($"Top now: {intStack.Peek()}"); // 20 var stringStack = new GenericStack<string>(); stringStack.Push("Hyderabad"); stringStack.Push("Webliance"); Console.WriteLine($"Top: {stringStack.Peek()}"); // Webliance |
Summary – What We Learned Today
- Generics → write one piece of code that works with any type
- Generic classes → class MyClass<T>
- Generic methods → void MyMethod<T>(T param)
- Constraints → where T : class, where T : new(), where T : IComparable<T>…
- Generic collections → List<T>, Dictionary<TKey,TValue>, HashSet<T>… – must-use in real apps
- Benefits → Type safety, performance, reusability, no casting
Your Homework (Super Practical!)
- Create a new console project called GenericsMaster
- Create a generic class called Pair<TFirst, TSecond> with:
- Properties: First, Second
- Method: Swap() (swaps First and Second)
- Create a generic method called PrintIfGreater<T> where T : IComparable<T> that takes two T values and prints the larger one
- In Program.cs:
- Use Pair<string, int> for name + age
- Use Pair<double, DateTime> for price + date
- Test PrintIfGreater with numbers, strings, and DateTime
Next lesson: LINQ (Language Integrated Query) – we’re going to learn how to query, filter, sort, and transform collections like magic!
You’re doing absolutely fantastic! 🎉 Any part confusing? Want more examples with constraints or multiple type parameters? Just tell me — I’m right here for you! 💙
