Generics Collection In C# Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    7 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of Generics collection in C#

Generics Collection in C# - Explained in Detail with Important Information

Important Aspects of Generics Collection in C#

1. Introduction and Benefits

  • Type Safety: Unlike non-generic collections (like ArrayList), generic collections ensure that only items of the specified type are added, reducing run-time errors.
  • Performance: Elimination of boxing and unboxing operations on value types, which can improve performance by avoiding additional memory allocations and copying.
  • Reusability: Code can be written to operate on any data type, which increases reusability.
  • Readability: Improves code readability and maintainability as the purpose of the collection is clearer.

2. Common Generic Collection Types

  • List: Represents a list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.
  • ArrayList: The non-generic counterpart to List<T>, storing data as objects, which requires boxing and unboxing of value types.
  • Dictionary<TKey, TValue>: Represents a collection of key-value pairs. Provides a high-performance way to look up values using keys.
  • Queue: Represents a first-in, first-out (FIFO) collection of objects.
  • Stack: Represents a last-in, first-out (LIFO) collection of objects.
  • HashSet: Represents a set of values, where each value must be unique.
  • SortedSet: Represents a set of values that are maintained in sorted order.
  • LinkedList: Represents a doubly linked list.

3. Initialization and Usage of Generic Collections

  • Declaring a Generic Collection:
    List<int> numbers = new List<int>();
    Dictionary<string, int> ages = new Dictionary<string, int>();
    
  • Adding Items:
    numbers.Add(10);
    ages.Add("John", 30);
    
  • Accessing Items:
    int firstNumber = numbers[0];
    int johnsAge = ages["John"];
    
  • Iterating Over a Collection:
    foreach (int number in numbers)
    {
        Console.WriteLine(number);
    }
    foreach (KeyValuePair<string, int> age in ages)
    {
        Console.WriteLine($"{age.Key}: {age.Value}");
    }
    

4. Advanced Features

  • Generics Constraints:

    • where T : class: T must be a reference type.
    • where T : struct: T must be a value type.
    • where T : unmanaged: T must be unmanaged (no references).
    • where T : new(): T must have a public parameterless constructor.
    • where T : BaseClass: T must be BaseClass or a derived class.
    • where T : IInterface: T must implement IInterface.
    public void Print<T>(T item) where T : class, new()
    {
        T instance = new T();
        Console.WriteLine(item);
    }
    
  • Generic Interfaces:

    • Implementing generic interfaces allows for type-safe methods to be defined that operate on generic types.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement Generics collection in C#

Example: Using Generics Collections

Step 1: Create a Simple Class

First, let’s define a simple class named Person for use in our collections.

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"Id: {Id}, Name: {Name}, Age: {Age}";
    }
}

Step 2: Using a Generic List

Now, let’s create a generic List<T> to store a list of Person objects, add some persons to the list, iterate through the list, and retrieve specific items from the list.

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        // Step 2: Create and Use a Generic List
        List<Person> people = new List<Person>();

        // Adding Persons to the list
        people.Add(new Person { Id = 1, Name = "Alice", Age = 30 });
        people.Add(new Person { Id = 2, Name = "Bob", Age = 25 });
        people.Add(new Person { Id = 3, Name = "Charlie", Age = 35 });

        // Iterating through the list
        Console.WriteLine("People in the List:");
        foreach (var person in people)
        {
            Console.WriteLine(person);
        }

        // Retrieving an item from the list by index
        Console.WriteLine("First person in the list:");
        Person firstPerson = people[0];
        Console.WriteLine(firstPerson);

        // Removing an item from the list
        people.Remove(firstPerson);
        Console.WriteLine("\nAfter removing Alice:");
        foreach (var person in people)
        {
            Console.WriteLine(person);
        }

        // Finding an item in the list using Find method
        Person foundPerson = people.Find(x => x.Name == "Bob");
        Console.WriteLine("\nFound person with name 'Bob':");
        if (foundPerson != null)
        {
            Console.WriteLine(foundPerson);
        }
        else
        {
            Console.WriteLine("No person with that name.");
        }
    }
}

Step 3: Using a Generic Dictionary

Next, we will work with a generic Dictionary<TKey, TValue> to map Person ids to their respective Person objects.

static void Main(string[] args)
{
    // Step 2: Create and Use a Generic List
    ...

    // Step 3: Create and Use a Generic Dictionary
    Dictionary<int, Person> personDictionary = new Dictionary<int, Person>();

    // Adding Persons to the Dictionary
    personDictionary.Add(people[0].Id, people[0]);
    personDictionary.Add(people[1].Id, people[1]);

    // Retrieving an item from the dictionary by key
    Console.WriteLine("\nPerson with id 2 in the dictionary:");
    Person personFromDict = personDictionary[2];
    Console.WriteLine(personFromDict);

    // Checking if a key exists in the dictionary
    bool keyExists = personDictionary.ContainsKey(3);
    Console.WriteLine($"\nDoes a person with id 3 exist in the dictionary? {keyExists}");

    // Removing an item from the dictionary
    personDictionary.Remove(2);
    Console.WriteLine("\nAfter removing person with id 2:");
    foreach (var kvp in personDictionary)
    {
        Console.WriteLine(kvp.Value);
    }
}

Step 4: Using a Generic Queue

Finally, we will use a generic Queue<T> to store and manipulate Person objects in a FIFO (first in, first out) order.

static void Main(string[] args)
{
    // Step 2: Create and Use a Generic List
    ...

    // Step 3: Create and Use a Generic Dictionary
    ...

    // Step 4: Create and Use a Generic Queue
    Queue<Person> personQueue = new Queue<Person>();

    // Enqueue Persons to the queue
    personQueue.Enqueue(people[0]);
    personQueue.Enqueue(people[1]);

    // Dequeue items from the queue (FIFO order)
    while (personQueue.Count > 0)
    {
        Person personFromQueue = personQueue.Dequeue();
        Console.WriteLine($"Dequeuing person: {personFromQueue}");
    }
}

Full Combined Code:

Here is the full combined code including all four steps:

Top 10 Interview Questions & Answers on Generics collection in C#

1. What are Generics Collections in C#?

Answer: Generics Collections in C# refer to a suite of classes that allow you to work with collections of objects while preserving type safety at compile time. Before generics, developers used non-generic collections like ArrayList from the System.Collections namespace, which could hold any type of object but did not provide type safety, potentially causing runtime errors if the wrong type was retrieved or used.

2. What is the difference between generics and non-generics collections?

Answer: Non-generic collections (e.g., ArrayList, Hashtable) can hold any type of object, which requires explicit casting when retrieving objects. This lack of type safety may lead to runtime errors. On the other hand, generic collections (e.g., List<T>, Dictionary<TKey, TValue>) enforce type safety at compile time, reducing the likelihood of runtime errors. Generic collections also perform better due to the absence of casting overhead.

3. How do you add elements to a List?

Answer: You can add elements to a List<T> using the Add() method. Here’s an example:

List<int> numbers = new List<int>();
numbers.Add(5);
numbers.Add(10);

4. Can you remove elements from a List? How?

Answer: Yes, you can remove elements from a List<T>. The Remove() method removes the first occurrence of a specific object, and RemoveAt() removes the element at a specified index. Examples:

List<int> numbers = new List<int> { 5, 10, 15 };
numbers.Remove(10); // Removes the first occurrence of 10
numbers.RemoveAt(0); // Removes the element at index 0

5. How can you iterate over a List?

Answer: You can iterate over a List<T> using a foreach loop or a traditional for loop. Here’s how:

Using foreach:

List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
foreach (string name in names)
{
    Console.WriteLine(name);
}

Using for loop:

for (int i = 0; i < names.Count; i++)
{
    Console.WriteLine(names[i]);
}

6. What are some common methods and properties of Dictionary<TKey, TValue>?

Answer: Common methods of Dictionary<TKey, TValue> include:

  • Add(TKey key, TValue value): Adds a key-value pair.
  • ContainsKey(TKey key): Determines whether the dictionary contains the specified key.
  • ContainsValue(TValue value): Determines whether the dictionary contains the specified value.
  • Remove(TKey key): Removes the element with the specified key.
  • TryGetValue(TKey key, out TValue value): Gets the value associated with the specified key.

Common properties include:

  • Count: Gets the number of key-value pairs contained in the Dictionary<TKey, TValue>.
  • Keys: Gets a collection containing the keys in the Dictionary<TKey, TValue>.
  • Values: Gets a collection containing the values in the Dictionary<TKey, TValue>.

Example usage:

Dictionary<string, int> ages = new Dictionary<string, int>();
ages.Add("Alice", 25);
ages.Add("Bob", 30);

if (ages.ContainsKey("Alice"))
{
    int age = ages["Alice"];
    Console.WriteLine(age); // Outputs 25
}

ages.Remove("Bob");
Console.WriteLine(ages.Count); // Outputs 1

7. How does HashSet differ from List?

Answer: A HashSet<T> stores unique elements and provides fast set operations such as adding, removing, and checking for existence. Unlike List<T>, HashSet<T> does not maintain order and does not allow duplicate values. Performance-wise, finding and inserting items into a HashSet<T> is generally faster than in a List<T> because HashSet<T> uses a hash table for storage.

Example:

HashSet<int> uniqueNumbers = new HashSet<int> { 1, 2, 3 };
bool added = uniqueNumbers.Add(2); // Returns false since 2 is already present
added = uniqueNumbers.Add(4); // Returns true since 4 was not present

8. Explain the Stack and Queue in C#.

Answer:

  • Stack: Represents a simple last-in, first-out (LIFO) collection. Use Push() to add an item to the top of the stack and Pop() to remove the item from the top. It exposes the Peek() method to look at the item on top without removing it.

    Example:

    Stack<int> stackNumbers = new Stack<int>();
    stackNumbers.Push(1);
    stackNumbers.Push(2);
    Console.WriteLine(stackNumbers.Peek()); // Outputs 2
    Console.WriteLine(stackNumbers.Pop());  // Outputs 2
    
  • Queue: Represents a first-in, first-out (FIFO) collection. Use Enqueue() to add an item to the end of the queue and Dequeue() to remove the item from the front. It exposes the Peek() method to return the item at the front without removing it.

    Example:

    Queue<int> queueNumbers = new Queue<int>();
    queueNumbers.Enqueue(1);
    queueNumbers.Enqueue(2);
    Console.WriteLine(queueNumbers.Peek()); // Outputs 1
    Console.WriteLine(queueNumbers.Dequeue()); // Outputs 1
    

9. What is the difference between List, ArrayList, and the other collection types?

Answer:

  • List: Generic collection providing type safety and better performance. Ideal for general-purpose list storage where type consistency is required.
  • ArrayList: Non-generic, holds items of any type, requiring boxing/unboxing for value types, which can decrease performance. It’s suitable only for scenarios requiring mixed-type objects or legacy systems.
  • LinkedList: A doubly linked list, efficient for insertions and deletions compared to List<T>. It is less efficient for accessing elements by index.
  • Queue: First-in, first-out (FIFO) collection ideal for scenarios where objects need to be processed in the order they are added.
  • Stack: Last-in, first-out (LIFO) collection useful when reversing order of elements or implementing backtracking algorithms.
  • Dictionary<TKey, TValue>: Stores key-value pairs, allowing fast lookups, insertions, and deletions by key. Not ordered but has O(1) average performance for these operations.

10. When should you use foreach loop vs. for loop with a List?

Answer:

  • foreach loop: Ideal for iterating over the complete collection when you do not need to modify the collection during iteration or need the current item but don’t care about its index. It makes the code cleaner and less error-prone.

    Example:

    List<string> fruits = new List<string> { "Apple", "Banana", "Cherry" };
    foreach (string fruit in fruits)
    {
        Console.WriteLine(fruit);
    }
    
  • for loop: Suitable when you need to iterate based on index positions, modify the collection while iterating, or perform conditional iterations based on index values. It provides more control but can be prone to off-by-one errors if not managed carefully.

    Example:

    for (int i = 0; i < fruits.Count; i++)
    {
        if (fruits[i].StartsWith("B"))
        {
            Console.WriteLine($"Found fruit starting with B: {fruits[i]}");
        }
    }
    

In summary, your choice between foreach and for should depend on whether you require index-based access and whether you plan to modify the list during iteration. For most scenarios involving just iteration, foreach is preferred for simplicity and readability.

You May Like This Related .NET Topic

Login to post a comment.