Generics in C#: Why They Are Needed and How to Use Them for Beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What Are Generics in C# and Why Are They Needed



Imagine you are writing a method that should work with different data types: integers, strings, objects. Without Generics, you would have to either duplicate code for each type or use object with subsequent type casting. Both approaches lead to problems: the first leads to redundancy, the second leads to runtime errors.



Generics (generic types) are a mechanism in C# that allows you to create classes, methods, interfaces, and delegates that work with any data type while maintaining strong typing. Instead of writing code for a specific type, you use a type parameter (usually denoted by the letter T), which is replaced with the actual type when used.



Main advantages of Generics:



  • Type safety — the compiler checks the correctness of type usage at compile time
  • Performance — no boxing/unboxing for value types
  • Code reuse — one class/method works with multiple types
  • Readability — code becomes cleaner and more understandable


The Problem Without Generics: Example with object



Before the advent of Generics, developers often used the object type to create universal methods. Consider an example:



public class Stack{    private object[] _items = new object[10];    private int _count = 0;        public void Push(object item) { _items[_count++] = item; }    public object Pop() { return _items[--_count]; }        // Usage    Stack stack = new Stack();    stack.Push(123);           // Boxing int to object    stack.Push("hello");       // Any type can be added    int number = (int)stack.Pop(); // Unboxing and casting}


The problems are obvious:

  • Explicit type casting is required when retrieving
  • You can accidentally add a value of a different type
  • For value types (int, double), boxing/unboxing occurs, which reduces performance


Basics of Working with Generics in C#



Creating a Generic Class



Consider the same stack example, but using Generics:



public class Stack<T>{    private T[] _items = new T[10];    private int _count = 0;        public void Push(T item) { _items[_count++] = item; }    public T Pop() { return _items[--_count]; }}

// UsageStack<int> intStack = new Stack<int>();intStack.Push(123);intStack.Push(456);int number = intStack.Pop(); // No unboxing needed, type is known

Stack<string> stringStack = new Stack<string>();stringStack.Push("Hello");// stringStack.Push(123); // Compilation error! Type safety


The parameter <T> is specified in angle brackets. When creating an instance, you specify the concrete type, and the compiler generates a specialized version of the class for that type.



Generic Methods



Generics work not only with classes but also with individual methods:



public class Utils{    public static T Max<T>(T a, T b) where T : IComparable<T>    {        return a.CompareTo(b) > 0 ? a : b;    }}

// Usageint maxInt = Utils.Max(5, 10); // 10string maxStr = Utils.Max("apple", "banana"); // "banana"


Constraints in Generics



Sometimes you need to restrict the types that can be used with a generic. For this, constraints are used with the where keyword:



// Constraint: T must be a reference typepublic class Repository<T> where T : class{                

Blogs

Book Recommendations