Generics and Generic Classes in C#

Last Updated : 26 Oct, 2026

Generics allow developers to create classes, methods, interfaces, and other types using type parameters instead of fixed data types. They make code reusable while providing compile-time type safety.

  • Allow the same code to work with different data types without duplication.
  • Detect type mismatches at compile time and reduce explicit casting.
  • Help avoid boxing and unboxing when working with value types.

Generics

Generics provide a way to define reusable components that can operate on different data types. Instead of writing separate classes or methods for integers, strings, and other types, developers can use a type parameter such as T.

For example, a generic class Box<T> can store an integer using Box<int> or a string using Box<string> without changing its implementation.

generic_collection
Generics class

Syntax

class ClassName<T>{
// Members using type T
}

Here:

  • T is a type parameter that acts as a placeholder for a data type.
  • The actual type, such as int or string, is provided when creating an object.
  • The compiler checks whether the supplied values match the specified type.

Generic Classes

A generic class is a class that uses one or more type parameters. It allows the same class definition to work with different data types without duplicating code.

Example: Program demonstrates a generic class that stores and displays values of different types.

C#
using System;

class Box<T> {
    private T value;

    public Box(T value) { this.value = value; }

    public void Display()
    {
        Console.WriteLine("Value: " + value);
    }
}

class Geeks {
    static void Main()
    {
        Box<int> intBox = new Box<int>(100);
        intBox.Display();

        Box<string> stringBox
            = new Box<string>("GeeksforGeeks");
        stringBox.Display();
    }
}

Output
Value: 100
Value: GeeksForGeeks

Explanation:

  1. Box<T> defines a generic class with a type parameter T.
  2. Box<int> creates an instance that stores an integer.
  3. Box<string> creates an instance that stores a string.
  4. The same class implementation works with both data types without changing its code.

Generic Classes with Multiple Type Parameters

A generic class can have multiple type parameters when it needs to work with more than one data type.

Syntax:

class ClassName<T1, T2>
{
// Members using T1 and T2
}

Example: The following program stores a student ID and name using two different type parameters.

C#
using System;

class Student<T1, T2>
{
    public T1 Id { get; set; }
    public T2 Name { get; set; }

    public Student(T1 id, T2 name)
    {
        Id = id;
        Name = name;
    }

    public void Display()
    {
        Console.WriteLine($"ID: {Id}, Name: {Name}");
    }
}

class Geeks
{
    static void Main()
    {
        Student<int, string> student =
            new Student<int, string>(101, "Rahul");

        student.Display();
    }
}

Output
ID: 101, Name: Rahul

Here, T1 represents the integer ID, while T2 represents the string name.

Generic Methods

A generic method uses a type parameter to perform the same operation on different data types. It can be defined inside either a generic or a non-generic class.

C#
using System;

class Geeks
{
    static void Display<T>(T value)
    {
        Console.WriteLine(value);
    }

    static void Main()
    {
        Display<int>(10);
        Display<string>("Hello");
        Display<double>(25.5);
    }
}

Output
10
Hello
25.5

The Display<T>() method accepts integers, strings, and doubles using the same implementation. The compiler can also infer the type argument, allowing calls such as Display(10).

Constraints in Generics

Generic constraints restrict the types that can be passed as type arguments. They are defined using the where keyword.

Common generic constraints include:

Constraint

Description

where T : class

Requires a reference type.

where T : struct

Requires a non-nullable value type.

where T : new()

Requires a public parameterless constructor.

where T : BaseClass

Requires inheritance from a specified base class.

where T : IInterface

Requires implementation of a specified interface.

where T : notnull

Requires a non-nullable type, with nullable warnings in an enabled nullable context.

Example: The following generic class accepts only types implementing IComparable<T>.

C#
using System;

class Calculator<T> where T : IComparable<T>
{
    public T GetMaximum(T a, T b)
    {
        return a.CompareTo(b) >= 0 ? a : b;
    }
}

class Geeks
{
    static void Main()
    {
        Calculator<int> calc = new Calculator<int>();

        Console.WriteLine(calc.GetMaximum(10, 20));
    }
}

Output
20

The IComparable<T> constraint allows the method to compare two values of type T using CompareTo().

Generic Collections

C# provides several built-in generic collection classes in the System.Collections.Generic namespace. These collections store elements using specified data types.

Collection

Description

List<T>

Stores a dynamically sized list of elements.

Dictionary<TKey, TValue>

Stores key-value pairs with unique keys.

Queue<T>

Processes elements in FIFO order.

Stack<T>

Processes elements in LIFO order.

HashSet<T>

Stores unique elements.

SortedSet<T>

Stores unique elements in sorted order.

Example: Using a generic List<T>.

C#
using System;
using System.Collections.Generic;

class Geeks
{
    static void Main()
    {
        List<int> numbers = new List<int>();

        numbers.Add(10);
        numbers.Add(20);
        numbers.Add(30);

        foreach (int number in numbers)
        {
            Console.WriteLine(number);
        }
    }
}

Output
10
20
30

Here, List<int> stores only integers. Attempting to add a string results in a compile-time error.

Advantages of Generics

Generics provide several benefits when developing reusable and type-safe applications.

  • Code Reusability: A single class or method can work with multiple data types, reducing duplicate implementations.
  • Type Safety: Type mismatches are detected at compile time, helping prevent invalid assignments.
  • Better Performance: Generic collections can avoid unnecessary boxing and unboxing of value types.
  • Reduced Casting: Strongly typed values generally eliminate the need for explicit casts when retrieving data.
  • Maintainability: Shared implementations make code easier to update and maintain.

Generics Vs Non-Generics

Generics

Non-Generics

Use type parameters such as T.

Use fixed types or general types such as object.

Provide compile-time type safety.

Object-based implementations may require runtime type checks.

Usually avoid explicit casting.

May require explicit casting.

Avoid unnecessary boxing for value types.

Object-based storage may involve boxing and unboxing.

Example: List<int>

Example: ArrayList

When to Use Generics?

Generics are useful when the same logic must work with different data types while maintaining type safety.

Common use cases include:

  • Data Collections: Store strongly typed elements using List<T>, Queue<T>, or Dictionary<TKey, TValue>.
  • Reusable Components: Create generic classes for data storage, processing, and result handling.
  • Common Algorithms: Implement searching, sorting, or comparison logic for different data types, using constraints where necessary.
Comment

Explore