← Back to Articles         
Generics Fundamentals

Generics Fundamentals

Published on 12 Oct 2026     11 min read C# Collections & Generics
Generics Fundamentals

1. What are generics in C#, and what are their advantages?

What are Generics?

Generics allow us to create reusable classes, methods, interfaces, and collections that work with different data types while maintaining compile-time type safety.

Instead of writing separate code for int, string, or other types, we can use a type parameter (T).

The actual data type is specified when using the generic class or method.

Example Program

Without Generics:

public class IntBox
{
    public int Value { get; set; }
}

public class StringBox
{
    public string Value { get; set; } = "";
}

We need separate classes for different data types.

With Generics:

public class Box<T>
{
    public T Value { get; set; }

    public Box(T value)
    {
        Value = value;
    }

    public void Display()
    {
        Console.WriteLine(Value);
    }
}

// Program
Box<int> intBox = new(100);
intBox.Display();

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

Box<double> doubleBox = new(25.5);
doubleBox.Display();

Output:

100
Hello
25.5

Program Explanation

  1. Box<T> defines a generic class.

  2. T represents a data type that will be provided later.

  3. Box<int> replaces T with int.

  4. Box<string> replaces T with string.

  5. Box<double> replaces T with double.

  6. The same class works with multiple data types without duplicating code.

How Generics Work

              Generic Class
                 Box<T>
                    |
         +----------+----------+
         |          |          |
         v          v          v
      Box<int>  Box<string> Box<double>
         |          |          |
         v          v          v
        100       "Hello"      25.5

       One Generic Definition
       Multiple Data Types

Advantages of Generics

Advantage

Explanation

Type safety

Detects invalid types at compile time

Code reusability

Same code works with different types

Performance

Avoids unnecessary boxing/unboxing

Maintainability

Reduces duplicate code

Strong typing

Reduces runtime casting errors

Key Points

  • Generics were introduced in C# 2.0.

  • T is a placeholder for a data type.

  • Generics support classes, methods, interfaces, delegates, and collections.

  • Common examples are List<T>, Dictionary<TKey,TValue>, and IEnumerable<T>.

  • Generics provide compile-time type safety.

Interview Tip: Generics improve code reusability, type safety, and performance, particularly when working with value types.

 

2. How do generics provide type safety and avoid boxing/unboxing?

Explanation

Generics provide two important benefits:

1. Type Safety

Generics ensure that only the specified data type can be used.

2. Avoid Boxing and Unboxing

Generic collections such as List<int> can store value types without converting them into object.

What is Boxing and Unboxing?

  • Boxing: Converting a value type into an object, which involves allocating a boxed object.

  • Unboxing: Extracting the value type from a boxed object through an explicit conversion.

Example Program

Without Generics:

using System.Collections;

ArrayList numbers = new();

numbers.Add(100);       // Boxing
numbers.Add(200);       // Boxing
numbers.Add("Hello");   // Allowed

int value = (int)numbers[0]; // Unboxing

Console.WriteLine(value);

Output:

100

Problems

  • ArrayList stores elements as object.

  • Adding integers requires boxing.

  • Retrieving integers requires unboxing.

  • Different types can be inserted into the collection.

  • Invalid casting can cause runtime exceptions.

For example:

int value = (int)numbers[2];

This throws InvalidCastException because the third element is a string.

With Generics

List<int> numbers = new();

numbers.Add(100);
numbers.Add(200);

// numbers.Add("Hello"); // Compile-time error

int value = numbers[0];

Console.WriteLine(value);

Output:

100

Program Explanation

  1. List<int> accepts only integers.

  2. Adding a string causes a compile-time error.

  3. Integers are stored without boxing each element.

  4. Retrieving an integer does not require an explicit cast.

  5. This improves type safety and avoids boxing overhead.

Internal Working

Without Generics:

ArrayList
    |
    v
Add(100)
    |
    v
Boxing
    |
    v
Object on Heap
    |
    v
Retrieve Value
    |
    v
Unboxing
    |
    v
int value = 100

With Generics:

List<int>
    |
    v
Add(100)
    |
    v
Store Integer Directly
    |
    v
Retrieve Integer
    |
    v
int value = 100

No Boxing/Unboxing Required

Key Points

  • Generics enforce compile-time type safety.

  • List<int> prevents inserting strings.

  • Generic collections avoid boxing/unboxing when storing and retrieving value-type elements.

  • Boxing creates an object allocation.

  • Unboxing requires an explicit conversion.

  • Avoiding boxing reduces unnecessary memory allocations and garbage collection overhead.

Important: Generics do not eliminate boxing in every situation. Boxing can still occur if a value type is explicitly converted to object or a non-generic interface.

Interview Tip: List<int> is generally preferable to ArrayList because it provides strong typing and avoids boxing/unboxing for stored integers.

 

3. What is the difference between a generic class and a generic method?

Explanation

Both use type parameters, but their scope is different.

Generic Class: A class that defines one or more type parameters.

Generic Method: A method that defines its own type parameters, even if the containing class is not generic.

Comparison

Feature

Generic Class

Generic Method

Type parameter declared at

Class level

Method level

Scope

Throughout the class

Within the method

Example

Repository<T>

Print<T>()

Type specified

When creating/using the class

When calling the method

Common use

Reusable repositories

Reusable utility methods

Example Program

Generic Class:

public class DataStore<T>
{
    private T _data;

    public DataStore(T data)
    {
        _data = data;
    }

    public void Display()
    {
        Console.WriteLine(_data);
    }
}

// Program
DataStore<int> store1 = new(100);
store1.Display();

DataStore<string> store2 = new("Hello");
store2.Display();

Output:

100
Hello

Here, T is defined at the class level and is available to its members.

Generic Method Example

public class Helper
{
    public void Print<T>(T value)
    {
        Console.WriteLine(value);
    }
}

// Program
Helper helper = new();

helper.Print<int>(100);
helper.Print<string>("Hello");
helper.Print<double>(25.5);

Output:

100
Hello
25.5

Program Explanation

  1. Helper is a normal class.

  2. Print<T>() is a generic method.

  3. The method accepts different data types.

  4. Each invocation can use a different type.

  5. The compiler can infer the type from the argument.

For example:

helper.Print(100);       // T = int
helper.Print("Hello");   // T = string

We do not always need to explicitly specify <T> when calling a generic method.

Internal Working

Generic Class
--------------------------

       DataStore<T>
            |
      +-----+-----+
      |           |
      v           v
DataStore<int> DataStore<string>
      |           |
      v           v
     100        "Hello"


Generic Method
--------------------------

          Helper
            |
            v
         Print<T>()
            |
      +-----+-----+
      |           |
      v           v
 Print<int>() Print<string>()
      |           |
      v           v
     100        "Hello"

Real-World Example

Generic classes are frequently used in repository patterns.

public class Repository<T>
{
    public void Add(T entity)
    {
        Console.WriteLine($"Added: {entity}");
    }
}

Usage:

Repository<string> repository = new();

repository.Add("Employee");

A generic method is useful for common operations such as logging or formatting values.

Key Points

  • Generic class type parameters are declared at class level.

  • Generic method type parameters are declared at method level.

  • A normal class can contain generic methods.

  • A generic class can also contain generic methods.

  • Generic methods often support type inference.

  • Generic classes are useful for repositories and reusable data structures.

Interview Tip: A generic class makes the entire class reusable across types, whereas a generic method makes a particular operation reusable across types.

 

4. What is a generic interface? Explain with an example.

What is a Generic Interface?

A generic interface defines a contract using one or more type parameters.

It allows different classes to implement the same contract for different data types.

Generic interfaces are commonly used in:

  • Repository patterns

  • Service layers

  • Dependency injection

  • Clean Architecture

  • Generic collections

Example Program

Suppose we want a common repository interface for different entities.

Step 1: Create a Generic Interface

public interface IRepository<T>
{
    void Add(T entity);
    T? GetById(int id);
}

T represents the entity type.

Step 2: Create Employee Class

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

Step 3: Implement Generic Interface

public class EmployeeRepository : IRepository<Employee>
{
    private readonly List<Employee> _employees = new();

    public void Add(Employee employee)
    {
        _employees.Add(employee);
    }

    public Employee? GetById(int id)
    {
        return _employees.FirstOrDefault(e => e.Id == id);
    }
}

Step 4: Use Repository

IRepository<Employee> repository = new EmployeeRepository();

repository.Add(new Employee
{
    Id = 101,
    Name = "John"
});

repository.Add(new Employee
{
    Id = 102,
    Name = "David"
});

Employee? employee = repository.GetById(102);

Console.WriteLine(employee?.Name);

Output:

David

Program Explanation

  1. IRepository<T> defines a reusable contract.

  2. EmployeeRepository implements IRepository<Employee>.

  3. The generic type T becomes Employee.

  4. Add() accepts only Employee objects.

  5. GetById() returns an Employee object or null.

  6. The caller works through the interface instead of depending directly on the implementation.

How Generic Interfaces Work

       IRepository<T>
              |
              v
       IRepository<Employee>
              |
              v
       EmployeeRepository
              |
       +------+------+
       |             |
       v             v
  Add(Employee)   GetById(id)
       |             |
       v             v
   Save Employee  Return Employee

Real-World Scenario: ASP.NET Core Dependency Injection

We can register the repository with the DI container.

builder.Services.AddScoped<
    IRepository<Employee>,
    EmployeeRepository>();

Then inject it into a service.

public class EmployeeService
{
    private readonly IRepository<Employee> _repository;

    public EmployeeService(IRepository<Employee> repository)
    {
        _repository = repository;
    }

    public Employee? GetEmployee(int id)
    {
        return _repository.GetById(id);
    }
}

This supports Dependency Inversion and makes the application easier to test.

Key Points

  • A generic interface defines a type-safe reusable contract.

  • IRepository<T> is a common example.

  • Different implementations can use different entity types.

  • Generic interfaces support abstraction and dependency injection.

  • They are widely used in Clean Architecture.

  • Built-in examples include IEnumerable<T>, ICollection<T>, IList<T>, and IComparer<T>.

Interview Tip: Generic interfaces are useful for designing reusable, loosely coupled application components, especially repositories and services.

 

5. What is the difference between generics and the object type?

Explanation

Both generics and object can be used to work with different data types.

However, they work differently.

object is the base type of all C# types and can hold values of different types.

Generics allow us to specify a particular type while retaining compile-time type safety.

Comparison

Feature

Generics

object

Type safety

Compile-time

Requires runtime checks/casts

Type parameter

Uses <T>

Uses object

Casting

Usually unnecessary

Often required

Boxing

Avoided for many value-type operations

Occurs for value-type conversion

Performance

Generally better for typed value operations

Can have boxing/casting overhead

Reusability

Yes

Yes

Common use

Collections, repositories

Mixed-type values, general APIs

Example Program

Using Object:

public class ObjectBox
{
    public object Value { get; set; }

    public ObjectBox(object value)
    {
        Value = value;
    }
}

// Program
ObjectBox box = new(100);

int number = (int)box.Value;

Console.WriteLine(number);

Output:

100

Problems

  • The stored value is exposed as object.

  • Explicit casting is needed to retrieve an integer.

  • Boxing occurs when passing an integer to the constructor.

  • Incorrect casting can cause runtime exceptions.

Using Generics

public class GenericBox<T>
{
    public T Value { get; set; }

    public GenericBox(T value)
    {
        Value = value;
    }
}

// Program
GenericBox<int> box = new(100);

int number = box.Value;

Console.WriteLine(number);

Output:

100

Program Explanation

  1. GenericBox<int> specifies the type as int.

  2. The constructor accepts an integer.

  3. The property returns an integer directly.

  4. No explicit cast is needed.

  5. No boxing is required for storing the integer in this generic property.

Internal Working

Using Object:

int value = 100
      |
      v
Convert to object
      |
      v
Boxing
      |
      v
ObjectBox.Value
      |
      v
Explicit Cast
      |
      v
Unboxing
      |
      v
int number = 100

Using Generics:

int value = 100
      |
      v
GenericBox<int>
      |
      v
Store as int
      |
      v
Retrieve as int
      |
      v
int number = 100

No Boxing or Explicit Cast

When should we use object?

object can still be useful when an API genuinely needs to handle unrelated types dynamically.

For example:

object value = "Hello";

if (value is string text)
{
    Console.WriteLine(text);
}

However, when the expected type is known, generics are usually preferable.

Key Points

  • object is the base type of all C# types.

  • Generics use type parameters to provide strong typing.

  • Generics reduce runtime casting errors.

  • Generics avoid unnecessary boxing/unboxing for value types.

  • Use generics when the data type can be specified.

  • Use object when genuinely heterogeneous values are required.

Interview Tip: The main advantages of generics over object are compile-time type safety, reduced casting, and better performance for many value-type scenarios.

 

Quick Interview Revision

Question

Important Answer

What are generics?

Reusable, type-safe code using type parameters

When were generics introduced?

C# 2.0

What does T represent?

Generic type parameter

Main advantages?

Type safety, reusability, performance

How do generics provide type safety?

Compile-time type checking

How do generics avoid boxing?

Store value types without converting to object

Generic class?

Class with type parameters

Generic method?

Method with its own type parameters

Can a normal class have generic methods?

Yes

Can generic methods infer types?

Yes, when arguments provide enough information

Generic interface?

Interface with type parameters

Example of generic interface?

IRepository<T>

Built-in generic interfaces?

IEnumerable<T>, IList<T>

Generics vs object?

Compile-time typing vs general object references

Are generics always faster?

No; performance depends on usage

Final Comparison

                  Generics in C#
                        |
          +-------------+-------------+
          |             |             |
          v             v             v
    Generic Class  Generic Method  Generic Interface
          |             |             |
          v             v             v
     Repository<T>    Print<T>()   IRepository<T>
          |             |             |
          +-------------+-------------+
                        |
                        v
                Type Safety
                        |
                        v
                Code Reusability
                        |
                        v
             Reduced Boxing/Casting

Most important for interviews: Understand why generics are preferred over object, how they prevent boxing/unboxing, the difference between generic classes and methods, and how generic interfaces such as IRepository<T> are used in real-world .NET applications.

← Back to Articles