Introduction to Generics in C#
Generics (generic types) are one of the most powerful features of the C# language, introduced in .NET Framework 2.0. They allow you to create classes, methods, and interfaces that work with any data type while maintaining strong typing. In this article, we will explore why generics are needed in C#, how to use them, and what advantages they provide to developers.
Imagine you need to write a method to swap the values of two variables. Without generics, you would have to create separate methods for each type: SwapInt, SwapString, SwapDouble, and so on. With generics, this task is solved with a single universal function.
Key Advantages of Generics
1. Type Safety at Compile Time
Generics provide type checking at compile time rather than at runtime. This means that errors related to type mismatches are detected early in the development process.
// Without generics - possible runtime errorArrayList list = new ArrayList();list.Add(123);list.Add("string");int number = (int)list[1]; // InvalidCastException!
// With generics - compile-time errorList<int> numbers = new List<int>();numbers.Add(123);numbers.Add("string"); // Compile-time error!2. Performance
Using generics avoids boxing and unboxing operations for value types. This significantly improves performance when working with collections.
// Boxing/Unboxing - slowArrayList list = new ArrayList();list.Add(42); // boxingint value = (int)list[0]; // unboxing
// Generics - fast, no boxingList<int> list = new List<int>();list.Add(42); // no boxingint value = list[0]; // no unboxing3. Code Reusability
A single generic class or method can work with any data type, eliminating code duplication.
Creating and Using Generics
Generic Classes
Consider an example of creating a generic class for storing a pair of values:
public class Pair<TFirst, TSecond>{ public TFirst First { get; set; } public TSecond Second { get; set; } public Pair(TFirst first, TSecond second) { First = first; Second = second; } public void Display() { Console.WriteLine($"First: {First}, Second: {Second}"); }}
// Usagevar pair1 = new Pair<int, string>(1, "one");var pair2 = new Pair<string, double>("PI", 3.14159);
pair1.Display(); // First: 1, Second: onepair2.Display(); // First: PI, Second: 3.14159Generic Methods
Methods can also be generic. Here is a classic example — a swap method:
public static void Swap<T>(ref T a, ref T b){ T temp = a; a = b; b = temp;}
// Usageint x = 5, y = 10;Swap<int>(ref x, ref y);Console.WriteLine($"x = {x}, y = {y}"); // x = 10, y = 5
string s1 = "Hello", s2 = "World";Swap(ref s1, ref s2); // type can be omittedConsole.WriteLine($"s1 = {s1}, s2 = {s2}"); // s1 = World, s2 = HelloGeneric Interfaces
Interfaces with generics allow creating flexible contracts:
public interface IRepository<T>{ T GetById(int id); void Add(T entity); void Update(T entity); void Delete(int id);}
public class UserRepository : IRepository<User>{ public User GetById(int id) { // Implementation for retrieving a user return new User(); } public void Add(User user) { // Implementation for adding } public void Update(User user) { // Implementation for updating }