Generics in C#: A Beginner's Guide

Generics in C#: A Beginner's Guide

03 Jul 2025
Beginner
2.16K Views
47 min read
Learn with an interactive course and practical hands-on labs

Best Free C# Course: Learn C# In 21 Days

Generics in C# allows you to write flexible, reusable code that works with any data type while maintaining type safety. They are unable to create a single method, class, or interface that can work with multiple types, such as integers, strings, or custom objects, without rewriting the logic. Whether you're a beginner or an experienced C# developer, mastering Generics will help you write cleaner and more efficient applications.

In this C# tutorial, you will learn everything you need to know about Generics in C# — what Generics are, their key benefits, how to use generic lists in C#, and the differences between Generics and Collections. You'll also discover how to create generic classes, generic methods, and generic collections to write more flexible, reusable, and type-safe C# code.

What Are Generics in C#?

Generics in C# allow you to define classes, interfaces, methods, or delegates with a placeholder for the data type. Generics allow you to handle different data types in a more organized, efficient way, reducing code duplication and enhancing maintainability.

Generics let you write code that can work with any data type — whether it’s a custom object, a string, or an int — without needing to rewrite the same logic for each type. This makes your code more adaptable and reusable. They also help to ensure type safety, meaning your code is checked at compile time to prevent errors caused by using incorrect types.

C# generics

You define generics using angle brackets < > and specify the actual type when you use the generic class or method. For example, you can create a generic list that can store any type of data, making it easy to manage values without duplicating code.

Features of Generics in C#

Generics provide several powerful features in c#:

Type Safety:

  • Generics ensure that only a specific type can be used, catching errors at compile time.

Code Reusability

  • You can write generic code once and reuse it for different types without duplicating logic.

Performance:

  • Generics improve performance by avoiding unnecessary boxing and unboxing, which enhances execution speed and memory usage.

Flexibility

  • Generics allow you to create data structures and algorithms that can work with any type, making your code more flexible.

Elimination of Type Casting:

  • When using generics, there’s no need to manually cast objects from one type to another (unlike non-generic collections such as ArrayList).

Maintainability

  • Code that uses generics is easier to read and maintain because the type relationships are clear and enforced.

Support for Custom Generic Classes and Methods

  • You can create your own generic classes, generic methods, generic interfaces, and even generic delegates.

Works with Collections and LINQ

  • Generics power most of the modern collection classes (List<T>, Dictionary<TKey, TValue>, etc.) and make LINQ queries type-safe.

Why Are Generics Important?

So, why are generics necessary? Let’s say you need a collection that holds integers, strings, and custom objects. You could use older non-generic collections like ArrayList, but they come with a major drawback: they aren’t type-safe.
This means you might accidentally add an item of the wrong type, causing errors at runtime. Generics fix this by letting you define what type your collection or class should hold, ensuring type safety and reducing bugs.
FeatureNon-Generics (ArrayList, etc.)Generics (List, Dictionary<TKey, TValue>, etc.)
Type SafetyNo, allows mixed typesYes, enforces compile-time type checking
PerformanceSlower (boxing/unboxing for value types)Faster (no boxing/unboxing)
Code ReusabilityLimitedHigh — reusable with any type
Type CastingManual casting requiredNo casting needed

Readability& Maintainability

Low-more prone to errors.

High — clear and type-safe
Compile-time CheckingLess reliable (runtime errors possible)Strong compile-time checks
Usage LINQNot supportedFully supported
ExamplesArrayList, HashtableList<T>,Dictionary<TKey,TValue>, Queue<T>

Core Concepts of Generic in C#

Generics are a valuable feature in C# that allows you to define classes, methods, delegates, and interfaces with placeholders for the types on which they operate. The following are the fundamental concepts of generics in C#:
1. Generic class
2. Generic methods
3. Generic Interfaces
4. Constraints in generic

1. Generic Class

  • A generic class in C# lets you define a class with a placeholder (called a type parameter) that works with multiple data types. Think of it like a cookie cutter that can make different shapes while using the same cutter.

Here’s a simple example


using System;
public class Box<T>
{
    public T Item { get; set; }
}

// Usage
class Program
{
    static void Main(string[] args)
    {
        var amitBox = new Box<int> { Item = 10 };
        Console.WriteLine("${amitBox.Item} is stored in Amit's box.");

        var priyaBox = new Box<string> { Item = "Hello, World!" };
        Console.WriteLine("${priyaBox.Item} is stored in Priya's box.");
    }
}

Output


10 is stored in Amit's box.
Hello, World! is stored in Priya's box.

Explanation

  • The Box<T> class allows us to store any type in it. The <T> is a placeholder for the type we define later.
  • We create instances of Box<int> and Box<string>, which means Box will only accept integers and strings, respectively.

2. Generic Method

  • You can also create generic methods that can handle different data types without repeating code.

using System;

public class Program
{
    public void Display<T>(T message)
    {
        Console.WriteLine(message);
    }

    static void Main(string[] args)
    {
        var program = new Program();
        program.Display(123);
        program.Display("Hello!");
        program.Display(45.67);
    }
}

Output


123
Hello!
45.67

Explanation

  • The Display<T> method can handle any data type by specifying the type parameter <T>.
  • In the Main method, the same Display method is used to print an integer, a string, and a double.

3. Generic Interfaces

  • Generic interfaces allow you to create flexible and reusable contracts that can be implemented by classes using different data types.
  • Let’s explore an example where we define a generic repository interface.

using System;
using System.Collections.Generic;

public interface IRepository<T>
{
    void Add(T item);
    List<T> GetAll();
}

public class Repository<T> : IRepository<T>
{
    private List<T> items = new List<T>();

    public void Add(T item) => items.Add(item);

    public List<T> GetAll() => items;
}

// Usage
class Program
{
    static void Main(string[] args)
    {
        IRepository<int> intRepo = new Repository<int>();
        intRepo.Add(5);
        Console.WriteLine("Repository contains:");
        foreach (int item in intRepo.GetAll())
        {
            Console.WriteLine(item);
        }
    }
}

Output

Repository contains:
5

Explanation

  • The IRepository<T> interface defines a contract for adding items and getting all items from a collection.
  • The Repository<T> class implements this interface and provides concrete functionality for adding and retrieving items.
  • In the Main method, we create a repository for integers and add a value to it, then print the value stored in the repository.

4. Constraints in Generics

Constraints help you limit the types that can be used as type parameters. For instance, you can enforce that a type must be a class or implement a specific interface.
using System;

public class Manager<T> where T : class
{
    public T Entity { get; set; }
}

Explanation

  • In this example, the Manager<T> class uses the where T: class constraint, ensuring that the type parameter T must be a reference type (class).
  • This makes sure that only reference types can be used, adding a layer of type safety to the generic class.

Real-Life Examples of Generics

Generics are commonly used in different scenarios:

1. Collections and Data Structures

  • Generics make collections like List<T>, Dictionary<TKey, TValue>, Queue<T>, and Stack<T> more efficient and type-safe. Let’s take a look:

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4 };
        Dictionary<int, string> students = new Dictionary<int, string>
        {
            { 1, "Amit" },
            { 2, "Priya" }
        };

        Console.WriteLine(numbers[0]);
        Console.WriteLine(students[1]);
    }
}

Output


1
Amit

Explanation

  • The List<int> stores a collection of integers, and the Dictionary<int, string> stores key-value pairs where the keys are integers, and the values are strings.
  • We print the first item in the list and the value associated with the key 1 in the dictionary.

2. Database Access

  • You can use generics to create reusable methods for accessing databases:

using System;
using System.Collections.Generic;

public class Repository<T>
{
    private List<T> items = new List<T>();

    public void Add(T item) => items.Add(item);
    public List<T> GetAll() => items;
}

// Usage
class Program
{
    static void Main(string[] args)
    {
        Repository<int> intRepository = new Repository<int>();
        intRepository.Add(1);
        intRepository.Add(2);
        List<int> allInts = intRepository.GetAll();
        Console.WriteLine("All integers in the repository:");
        foreach (int i in allInts)
        {
            Console.WriteLine(i);
        }
    }
}

Output


All integers in the repository:
1
2

Explanation

  • The Repository<T> class can store and retrieve data of any type <T>.
  • In the Main method, we create a repository for integers, add two integers, and retrieve all the integers stored in the repository.

3. Constraints in Generics

  • Sometimes, you want to restrict the types that can be used in a generic class or method.
  • This is where constraints come into play.
  • You can specify that the type parameter must implement a specific interface, inherit a particular class, or have a default constructor.

Let’s look at an example:


using System;

public class Person
{
    public string Name { get; set; }
}

public class Employee : Person
{
    public int EmployeeId { get; set; }
}

public class EmployeeRepository<T> where T : Person
{
    public void Add(T person)
    {
        Console.WriteLine("${person.Name} added!");
    }
}

// Usage
class Program
{
    static void Main(string[] args)
    {
        var employeeRepo = new EmployeeRepository<Employee>();
        employeeRepo.Add(new Employee { Name = "John", EmployeeId = 101 });
    }
}

Output

John added!

Explanation

  • The EmployeeRepository<T> class is constrained by the where T: Person constraint, meaning it only accepts types that inherit from the Person class.
  • In the Main method, we add an Employee object to the EmployeeRepository class.

Code Example of Feature of Generic :

1. Type Safety

  • Generics provide type safety, ensuring that only a specific type can be used.
  • This reduces runtime errors because incorrect types can’t be accidentally added to a generic collection or passed to a method.

Example: Type Safety in Generic List


using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<int> numberList = new List<int> { 1, 2, 3 };
        // numberList.Add("Hello"); // This line will cause a compile-time error

        Console.WriteLine(numberList[0]); // Output: 1
    }
}

Output

1

Explanation

  • You can’t accidentally add a string to numberList because it only accepts integers.
  • C# enforces this rule at compile time, catching the error early and preventing potential runtime issues.

2. Performance

  • Generics improve performance by avoiding unnecessary boxing and unboxing of value types.
  • Boxing occurs when a value type is converted to an object, and unboxing occurs when an object is converted back to a value type.
  • Generics eliminate this need, making your code faster.

Example: Avoiding Boxing with Generics


using System;
using System.Collections;

class Program
{
    static void Main(string[] args)
    {
        // Without Generics
        ArrayList list = new ArrayList();
        list.Add(10); // Boxing happens
        int number = (int)list[0]; // Unboxing happens

        // With Generics
        List<int> genericList = new List<int> { 10 };
        int genericNumber = genericList[0]; // No boxing or unboxing
    }
}

Explanation

  • Using a List<int> avoids boxing and unboxing, which improves memory usage and execution speed.
  • Boxing occurs when a value type is converted to an object, and unboxing occurs when an object is converted back to a value type.

3. Binary Code Reuse

  • Generics allow you to write code that works with different types without repeating yourself.
  • This means less code duplication and easier maintenance.

Example: Reusable Generic Class


using System;

public class Pair<T1, T2>
{
    public T1 First { get; set; }
    public T2 Second { get; set; }
}

// Usage
class Program
{
    static void Main(string[] args)
    {
        var intStringPair = new Pair<int, string> { First = 1, Second = "One" };
        Console.WriteLine(${intStringPair.First}, {intStringPair.Second}); // Output: 1, One
    }
}

Output

1, One

Explanation

  • You create a generic class Pair<T1, T2> that can hold two related values of different types.
  • This allows you to avoid writing separate classes for each possible combination of types.

Role of Collections in Generics: Dictionary, Queue, and Stack

  • In C#, collections like Dictionary, Queue, and Stack utilize generics to enhance code flexibility and type safety.
  • These data structures allow developers to create type-safe collections that efficiently manage data while minimizing runtime errors.

1. Dictionary in C#

  • A Dictionary in C# is a collection that stores key-value pairs.
  • It allows fast lookups by key and enforces uniqueness for the keys.

Example: Dictionary Usage


using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary students = new Dictionary
        {
            { 1, "Amit" },
            { 2, "Priya" }
        };
        
        Console.WriteLine(students[1]); // Output: Amit
        Console.WriteLine(students[2]); // Output: Priya
    }
}

Output

Amit
Priya

Explanation

  • A Dictionary allows you to store key-value pairs. The keys must be unique, and in this example, the key is the student's ID, and the value is their name.

Queues in C#

  • A Queue in C# is a first-in, first-out (FIFO) collection that processes items in the order they were added.

Example: Queue Usage

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Queue queue = new Queue();
        queue.Enqueue("First");
        queue.Enqueue("Second");
        queue.Enqueue("Third");
        
        Console.WriteLine(queue.Dequeue()); // Output: First
        Console.WriteLine(queue.Dequeue()); // Output: Second
    }
}

Output

First
Second

Explanation

  • A Queue processes elements in the order they were added.
  • Here, items are added using Enqueue() and removed using Dequeue().

3. Stacks in C#

  • A Stack in C# is a last-in, first-out (LIFO) collection.
  • The last element added is the first one to be removed.

Example: Stack Usage

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Stack stack = new Stack();
        stack.Push("First");
        stack.Push("Second");
        stack.Push("Third");
        
        Console.WriteLine(stack.Pop()); // Output: Third
        Console.WriteLine(stack.Pop()); // Output: Second
    }
}

Output

Third
Second

Explanation

  • A Stack follows a last-in, first-out order.
  • Items are added using Push() and removed using Pop().

    Difference between Generic and Collection

    Generics are a language feature in C# that allows you to write classes, methods, interfaces, and delegates with a placeholder for the data type.
    Collections in C# are data structures that store, organize, and manage groups of objects in memory. They help you handle lists of items, queues, stacks, key-value pairs, and more. C#
    In short:
    • Generics: Feature to create reusable and type-safe code.
    • Collections: Structures to store and manage groups of data — often built using Generics
    PointGenerics in C#Collections in C#
    DefinitionLanguage feature — allows defining type-safe, reusable code (classes, methods, interfaces) with placeholders for typesData structures used to store and manage groups of objects (like lists, queues, dictionaries)
    PurposeTo write flexible and type-safe codeTo organize and manipulate multiple data elements.
    ExamplesT, TKey, TValue, <T>List, ArrayList, Dictionary, Queue, Stack
    RelationCan be used within collections (ex: List<T>, Dictionary<TKey, TValue>)Collections use Generics to provide type-safe storage.
    Type ParameterizationYes — placeholders like <T> usedNon-generic collections don’t use type parameters
    Use CaseBuilding reusable libraries, algorithms, utility classesManaging and storing groups of objects

    Advantages of Generics in C#

    • Reusability: You write a generic class or method once, and it works with multiple data types.
    • Type Safety: You prevent errors by enforcing type checks during compile time.
    • Performance: Generics reduce the need for type conversions, improving speed and efficiency.

    Common Pitfalls and Best Practices of Generic in C#

    • Overusing Generics: Not every problem requires a generic solution. Use them only where necessary.
    • Ignoring Constraints: Applying constraints makes your code safer and easier to use.
    Read More: Top 50 C# Interview Questions & Answers To Get Hired
    Conclusion

    Generics in C# are an essential feature that empowers you to write flexible, reusable, and type-safe code. Whether you're working with collections, building custom data structures, or implementing methods and classes that function with different data types. Generics help reduce code duplication and improve maintainability. By mastering Generics, you’ll produce cleaner, more efficient, and scalable applications.

    Ready to take your C# skills to the next level? Join ScholarHat’s comprehensive C# Programming Course and deepen your understanding of advanced topics like Generics, Reflection, LINQ, and dynamic application development.

    FAQs

    The purpose of generics in C# is to let you create flexible, reusable code that can work with any data type while ensuring type safety. It helps you avoid runtime errors by enforcing type constraints at compile time, making your code more reliable and efficient.

    Generics are type-safe in C# because they allow you to define classes, methods, or interfaces without specifying an exact data type. This means you can enforce type constraints at compile time, preventing invalid data types from being used. It ensures that you work with the correct types, reducing runtime errors and making your code more reliable.

    The benefits of generics include type safety, code reusability, and better performance. Generics help you catch type-related errors at compile time, reducing runtime issues. They allow you to write flexible code that works with different data types without duplication. Additionally, generics improve performance by avoiding unnecessary boxing and unboxing of value types.

    Take our Csharp skill challenge to evaluate yourself!

    In less than 5 minutes, with our skill challenge, you can identify your knowledge gaps and strengths in a given skill.

    GET FREE CHALLENGE

    Share Article
    About Author
    Shailendra Chauhan (Microsoft MVP, Founder & CEO at ScholarHat)

    Shailendra Chauhan, Founder and CEO of ScholarHat by DotNetTricks, is a renowned expert in System Design, Software Architecture, Azure Cloud, .NET, Angular, React, Node.js, Microservices, DevOps, and Cross-Platform Mobile App Development. His skill set extends into emerging fields like Data Science, Python, Azure AI/ML, and Generative AI, making him a well-rounded expert who bridges traditional development frameworks with cutting-edge advancements. Recognized as a Microsoft Most Valuable Professional (MVP) for an impressive 9 consecutive years (2016–2024), he has consistently demonstrated excellence in delivering impactful solutions and inspiring learners.

    Shailendra’s unique, hands-on training programs and bestselling books have empowered thousands of professionals to excel in their careers and crack tough interviews. A visionary leader, he continues to revolutionize technology education with his innovative approach.
    Live Training - Book Free Demo
    React Certification Training
    12 Jul
    07:00AM - 09:00AM IST
    Checkmark Icon
    Get Job-Ready
    Certification
    Azure Developer Certification Training
    14 Jul
    07:00AM - 09:00AM IST
    Checkmark Icon
    Get Job-Ready
    Certification
    Azure AI Engineer Certification Training
    17 Jul
    07:00AM - 09:00AM IST
    Checkmark Icon
    Get Job-Ready
    Certification
    Azure AI & Gen AI Engineer Certification Training Program
    17 Jul
    07:00AM - 09:00AM IST
    Checkmark Icon
    Get Job-Ready
    Certification
    Advanced Full-Stack .NET Developer with Gen AI Certification Training
    20 Jul
    09:30AM - 11:30AM IST
    Checkmark Icon
    Get Job-Ready
    Certification
    Accept cookies & close this