Introduction to Generics
Vaibhav • September 11, 2025
In earlier chapters, we explored collections like List<T>
and methods
that operate on specific types. But what if you want to write code that works across many types-without
sacrificing type safety or performance? That’s where generics come in. Generics let you define
classes, methods, and interfaces with a placeholder for the type, so your code becomes reusable, flexible, and
safe.
Why Generics Matter
Imagine writing a method that swaps two values. You could write one for int
,
another for string
, and so on. But that’s tedious and error-prone. Generics let
you write it once and use it for any type:
// Generic swap method
void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
Here, T
is a type parameter. When you call Swap
, the compiler replaces T
with the actual
type you use-like int
or string
. This gives
you strong typing and avoids boxing/unboxing or runtime type checks.
Generics were introduced in C# 2.0 and are now a cornerstone of modern C# development. They power collections, LINQ, delegates, and even async programming.
Type Safety Without Repetition
Before generics, developers used object
to write reusable code. But that meant
losing type safety and needing casts:
// Non-generic version
object value = 42;
int number = (int)value; // Risky: runtime error if wrong type
With generics, the compiler enforces type correctness at compile time:
// Generic version
List<int> numbers = new List<int>();
numbers.Add(42); // Safe
int n = numbers[0]; // No cast needed
This eliminates a whole class of bugs and makes your code easier to read and maintain.
Generic Type Parameters
A generic type parameter is a placeholder for a type. You define it using angle brackets <T>
. The name T
is conventional, but you
can use any identifier. For example:
// Generic class
public class Box<T>
{
public T Value;
}
You can now create boxes for any type:
Box<int> intBox = new Box<int>();
intBox.Value = 123;
Box<string> strBox = new Box<string>();
strBox.Value = "Hello";
The compiler ensures that intBox
only holds integers, and strBox
only holds strings.
Generics in the Real World
You’ve already used generics without realizing it. The List<T>
class is
generic. So are Dictionary<K,V>
, Queue<T>
, and Stack<T>
. Even LINQ
methods like Select
and Where
are generic.
List<string> names = new List<string> { "Alice", "Bob" };
List<int> scores = new List<int> { 90, 85 };
These collections are strongly typed, so you get IntelliSense, compile-time checks, and no need for casting.
Generics are not just for collections. You can use them in your own classes, methods, interfaces, and delegates. They’re a powerful abstraction tool.
Generic Methods vs Generic Classes
You can define generics at the class level or method level. Use class-level generics when the type is part of the object’s identity. Use method-level generics when the type is only relevant to a specific operation.
// Generic method inside non-generic class
public class Utility
{
public static void Print<T>(T item)
{
Console.WriteLine(item);
}
}
This method works for any type, even though the class itself isn’t generic.
Constraints and Flexibility
Sometimes you want to restrict what types can be used with a generic. That’s where constraints
come in. For example, you might require that T
implements an interface or has a
parameterless constructor. We’ll explore constraints in the next article.
Performance Benefits
Generics avoid boxing and unboxing for value types. That means better performance and fewer allocations. For example:
// Non-generic collection
ArrayList list = new ArrayList();
list.Add(42); // Boxed
int x = (int)list[0]; // Unboxed
// Generic collection
List<int> list2 = new List<int>();
list2.Add(42); // No boxing
int y = list2[0]; // No unboxing
This matters in performance-critical code like games, simulations, or large-scale data processing.
Prefer generics over object
or non-generic collections.
They’re safer, faster, and easier to maintain.
Common Pitfalls
- Using object instead of generics: leads to casting and runtime errors.
- Overusing generics: don’t make everything generic-use them when type flexibility is needed.
- Ignoring constraints: without constraints, you may get compile-time errors when using certain operations.
Summary
Generics let you write reusable, type-safe code that works across many types. They eliminate the need for
casting, improve performance, and make your APIs cleaner. You’ve already used generics in collections like List<T>
, and now you understand how to define your own generic classes and
methods. In the next article, we’ll explore Generic Classes in depth-how to design them, when
to use them, and how they interact with other features like inheritance and interfaces.