Generics and Typing in C#: Why You Can't Do Without Them?
Imagine you're building a house, but every time you need to hammer a nail, you carve a new hammer specifically for that nail. Absurd? That's exactly what code looks like without generics. In C#, Generics are that universal hammer that fits any task without losing precision.
Today we'll break down how Generics and typing in C# transform your code from a collection of scattered crutches into a coherent, flexible, and safe system. You'll learn how to write generic methods and classes that work with any data type without sacrificing performance. Let's go!
What are Generics and Why Do You Need Them?
Generics are a mechanism that allows you to create classes, methods, and interfaces that work with any data type while maintaining strict typing at compile time. Sounds complicated? In practice, it's simple.
Before Generics appeared in C# (version 2.0, 2005), developers used two approaches:
- Copy-paste — creating a separate class for each type (int, string, double, etc.).
- object — using the base object type, which led to boxing/unboxing and performance loss.
Both options are bad. The first wastes time, the second wastes performance. Generics solve both problems at once.
What Does Generics Look Like in Practice?
// Without Generics: copy-paste for each typepublic class IntList{ private int[] items = new int[10]; public void Add(int item) { /* ... */ }}
public class StringList{ private string[] items = new string[10]; public void Add(string item) { /* ... */ }}
// With Generics: one class for all casespublic class GenericList<T>{ private T[] items = new T[10]; public void Add(T item) { /* ... */ }}
// Usagevar intList = new GenericList<int>();var stringList = new GenericList<string>();Notice: GenericList<T> is a template. You substitute any type (int, string, your own class) for T, and the compiler creates a specific version of the code for that type. No duplication, no object.
Typing in C#: Static, Strong, Safe
Before diving deeper, remember that C# is a statically typed language. This means the type of each variable is determined at compile time. Generics don't break this rule; on the contrary, they strengthen it.
When you write List<int>, the compiler knows exactly that the list contains only integers. Try to add a string — you'll get a compile-time error, not a runtime exception. This is type safety.
Generics vs object: The Performance Battle
// Bad example with objectArrayList badList = new ArrayList();badList.Add(123); // Boxing int -> objectint value = (int)badList[0]; // Unboxing object -> int
// Good example with GenericsList<int> goodList = new List<int>();goodList.Add(123); // No boxingint value = goodList[0]; // No unboxingFor value types (int, double, struct), the performance difference can be 10-20 times. For reference types (class), there's no boxing, but Generics still win due to the absence of type casting.
Generic Methods: Flexibility Without Extra Code
Generics work not only with classes but also with methods. Suppose you need to write a method that swaps two elements. Without Generics, you'd have to write a bunch of overloads:
// Without Genericspublic void SwapInt(ref int a, ref int b) { int temp = a; a = b; b = temp; }public void SwapString(ref string a, ref string b) { string temp