Write reusable, type-safe code with generic classes, methods, and collections.
Generics are a powerful feature in C# that allow you to write flexible, reusable code without sacrificing type safety. Instead of repeating code for every data type, you can define a class, method, or interface once β and use it with any type you need.
This lesson explains how generics work, why theyβre useful, and how to use them effectively in your own projects.
π What Are Generics?
Generics let you write code that works with any data type, while still preserving compile-time type checking.
Instead of writing:
public class IntBox
{
public int Value;
}
public class StringBox
{
public string Value;
}
You write:
public class Box<T>
{
public T Value;
}
Now Box<int> or Box<string> can be created from the same class, with strong typing.
π― Why Use Generics?
β
Type Safety
You avoid runtime casting and errors:
Box<int> numberBox = new Box<int>();
numberBox.Value = 42;
β
Code Reuse
Write once β use with any type.
β
Performance
Avoid boxing/unboxing (converting value types to object and back again).
β
Cleaner APIs
Collections like List<T> and Dictionary<TKey, TValue> rely on generics to be flexible but strongly typed.
π¦ Defining a Generic Class
public class Box<T>
{
public T Content { get; set; }
public void Print()
{
Console.WriteLine($"Box contains: {Content}");
}
}
πΉ Example Usage:
Box<string> messageBox = new Box<string> { Content = "Hello!" };
messageBox.Print(); // Output: Box contains: Hello!
Box<int> intBox = new Box<int> { Content = 99 };
intBox.Print(); // Output: Box contains: 99
You can create as many versions as you like, and each one will behave exactly like a class written for that type.
βοΈ Generic Methods
You donβt always need to make a class generic β you can just create generic methods.
public class Helper
{
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}
πΉ Example:
int x = 5, y = 10;
Helper.Swap(ref x, ref y);
Console.WriteLine($"{x}, {y}"); // Output: 10, 5
This works with any type: int, string, even custom classes.
π Constraints β Limiting Generic Types
You can restrict what types can be used with your generic classes or methods using constraints.
πΉ Where Clause Example:
public class Repository<T> where T : class
{
public void Save(T item)
{
Console.WriteLine("Item saved.");
}
}
Common Constraints:
| Constraint | Meaning |
|---|---|
where T : class | Only reference types allowed |
where T : struct | Only value types allowed |
where T : new() | Must have a public parameterless constructor |
where T : BaseClass | Must inherit from BaseClass |
where T : Interface | Must implement Interface |
π§ͺ Real-World Example: Generic Response Wrapper
public class ApiResponse<T>
{
public bool Success { get; set; }
public T Data { get; set; }
public string Message { get; set; }
}
Usage:
ApiResponse<string> result = new ApiResponse<string>
{
Success = true,
Data = "User created",
Message = "Operation successful"
};
Or:
ApiResponse<User> userResult = new ApiResponse<User>
{
Success = true,
Data = new User { Username = "james" },
Message = "Found user"
};
You only define the structure once, and reuse it anywhere.
π« Without Generics: The Old Way
Before generics, developers used object, but this caused runtime errors and messy casting:
ArrayList list = new ArrayList();
list.Add("Hello");
string word = (string)list[0]; // Risky cast
β With generics:
List<string> list = new List<string>();
list.Add("Hello");
string word = list[0]; // Safe, no cast needed
π Summary Table
| Feature | Description | Example |
|---|---|---|
T | Generic type placeholder | Box<T> |
| Generic Class | Class usable with any data type | public class Box<T> |
| Generic Method | Method usable with any type | public void Swap<T>() |
| Constraint | Limit allowed types for generics | where T : class |
| Reuse | Replaces code repetition across data types | List<T>, Dictionary<K,V> |
π§ͺ Challenge Task
Create a class Pair<T1, T2> that:
- Stores two values of any type
- Has a method
Describe()that returns a string like:"Pair contains: [First: 123, Second: Hello]"
Then try it with:
Pair<int, string> combo = new Pair<int, string>(123, "Hello");
combo.Describe();
β Best Practices
β
Name generic parameters T, TKey, TValue, TResult, etc.
β
Use constraints to guide generic usage
β
Avoid boxing/unboxing by using generics with value types
β
Keep generics lean and focused β donβt over-engineer
β
Rely on List<T>, Dictionary<K,V>, and similar .NET generics when possible
π Want to see how generics integrate with LINQ, Entity Framework, or Web APIs? Our advanced C# development course walks through dozens of real-world examples using generic collections and models.