# Mastering C# Part 5.1 Collections (Part-1)

---

In software development, data management is a crucial aspect of building efficient and scalable applications. Collections in C# provide a way to group, store, and manipulate data in a structured manner. They are essential for handling dynamic datasets, offering powerful tools for developers to work with.

C# collections can be broadly categorized into **generic collections** (from the `System.Collections.Generic` namespace) and **non-generic collections** (from the `System.Collections` namespace). Generic collections offer type safety and better performance, while non-generic collections are more flexible but less type-safe.

In this article, we'll dive into both types of collections, exploring their features, advantages, and examples. Whether you're dealing with lists, dictionaries, queues, or stacks, you'll find a suitable collection type for your needs in C#. So, let's get started!

Here are the collections that uses generic namespace

* `HashSet<T>`
    
* `LinkedList<T>`
    
* `List<T>`
    
* `SortedSet<T>`
    
* `Dictionary<TKey, TValue>`
    
* `SortedDictionary<TKey, TValue>`
    
* `Stack<T>`
    
* `Queue<T>`
    

### HashSet&lt;T&gt;

`HashSet<T>` is a **generic collection** in C# that ensures all elements are **unique** and is designed for fast lookups. It doesn’t maintain any order of elements and provides efficient operations like add, remove, and search.

---

**Key Features**

1. **No duplicates**: Prevents adding the same element multiple times.
    
2. **Unordered**: Elements are stored without a specific sequence.
    
3. **Fast operations**: Adding, removing, and searching are very fast (average O(1) time complexity).
    

---

**Examples**

Here’s a short example to understand its usage:

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a HashSet
        HashSet<string> fruits = new HashSet<string>();

        // Add elements
        fruits.Add("Apple");
        fruits.Add("Banana");
        fruits.Add("Apple"); // Duplicate, won't be added

        // Display elements
        Console.WriteLine("Fruits in HashSet:");
        foreach (var fruit in fruits)
        {
            Console.WriteLine(fruit); // Output: Apple, Banana (unordered)
        }
    }
}
```

---

**Common operations**

1. **Check for an item**:
    
    ```csharp
    Console.WriteLine(fruits.Contains("Banana")); // Output: True
    ```
    
2. **Remove an item**:
    
    ```csharp
    fruits.Remove("Apple");
    ```
    
3. **Perform set operations** (like union and intersection):
    
    ```csharp
    HashSet<string> moreFruits = new HashSet<string> { "Banana", "Cherry" };
    fruits.UnionWith(moreFruits); // Adds Cherry to `fruits`
    ```
    

---

**When to use?**

* To maintain a **collection of unique items** (e.g., IDs, names).
    
* When **quick lookups** are required.
    

---

## LinkedList&lt;T&gt;

`LinkedList<T>` is a **generic collection** in the `System.Collections.Generic` namespace that implements a **doubly linked list**. Each element (or node) contains a reference to both its predecessor and successor, enabling efficient insertion and removal operations.

---

**Key Features**

1. **Fast Insertion and Removal**: Inserting or removing elements at any position (beginning, middle, or end) is fast, as it doesn’t require shifting other elements.
    
2. **Doubly Linked**: Each node points to both the previous and next nodes, making traversal bidirectional.
    
3. **Individually Allocated Nodes**: Each element is stored as a separate object, which can increase memory overhead.
    

---

**Performance Considerations**

1. **Strengths**:
    
    * Best for **frequent insertions or deletions** at the middle of the list.
        
    * No need to copy the entire collection during modifications.
        
2. **Weaknesses**:
    
    * **Higher memory usage** compared to arrays or lists (due to references).
        
    * **Sequential access**: Slower when accessing elements by index (no direct indexing like `List<T>`).
        

---

**Examples**

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a LinkedList
        LinkedList<string> animals = new LinkedList<string>();

        // Add elements
        animals.AddLast("Dog");
        animals.AddLast("Cat");
        animals.AddFirst("Bird"); // Add at the start

        // Display elements
        Console.WriteLine("Animals in LinkedList:");
        foreach (var animal in animals)
        {
            Console.WriteLine(animal); // Output: Bird, Dog, Cat
        }

        // Insert after a specific node
        LinkedListNode<string> dogNode = animals.Find("Dog");
        if (dogNode != null)
        {
            animals.AddAfter(dogNode, "Rabbit");
        }

        // Remove a specific node
        animals.Remove("Cat");

        // Display updated list
        Console.WriteLine("\\nUpdated LinkedList:");
        foreach (var animal in animals)
        {
            Console.WriteLine(animal); // Output: Bird, Dog, Rabbit
        }
    }
}
```

---

**When to use LinkedList?**

* **Frequent insertions/deletions**: Use when you frequently add/remove elements, especially in the middle of the list.
    
* **Dynamic size**: Preferable when the size of the collection changes frequently.
    
* Avoid when random access by index is needed; use `List<T>` instead.
    

---

## List&lt;T&gt;

`List<T>` is a generic collection in the `System.Collections.Generic` namespace. It represents a **resizable array** that allows:

1. **Indexed Access**: Elements can be accessed using an index.
    
2. **Dynamic Resizing**: Automatically resizes when adding elements.
    
3. **Versatility**: Can hold any type of data, including custom objects.
    

---

**Key Features**

1. **Index-Based Access**: Quickly access elements using an index.
    
2. **Dynamic**: No need to specify a fixed size; it grows/shrinks as needed.
    
3. **Methods for Manipulation**: Includes methods like `Add`, `Remove`, `Sort`, and `Find`.
    

---

**Example**

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a List of integers
        List<int> numbers = new List<int>();

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

        // Access by index
        Console.WriteLine($"First element: {numbers[0]}"); // Output: 10

        // Insert element at a specific position
        numbers.Insert(1, 15); // Adds 15 at index 1

        // Remove element
        numbers.Remove(20); // Removes the first occurrence of 20

        // Display elements
        Console.WriteLine("Numbers in List:");
        foreach (var number in numbers)
        {
            Console.WriteLine(number); // Output: 10, 15, 30
        }

        // Sort the List
        numbers.Sort();
        Console.WriteLine("\\nSorted List:");
        foreach (var number in numbers)
        {
            Console.WriteLine(number); // Output: 10, 15, 30
        }
    }
}
```

---

**Common Operations**

1. **Add an item**:
    
    ```csharp
    numbers.Add(40); // Adds 40 to the end of the list.
    ```
    
2. **Remove an item**:
    
    ```csharp
    numbers.Remove(10); // Removes the first occurrence of 10.
    ```
    
3. **Find an item**:
    
    ```csharp
    int found = numbers.Find(x => x > 20); // Finds the first element > 20.
    ```
    
4. **Access by index**:
    
    ```csharp
    int first = numbers[0]; // Accesses the first element.
    ```
    
5. **Sort the list**:
    
    ```csharp
    numbers.Sort(); // Sorts the list in ascending order.
    ```
    

---

**When to use List&lt;T&gt;?**

* **Dynamic size**: Use when the size of the collection changes frequently.
    
* **Indexed access**: Ideal when you need fast access to elements by index.
    
* Avoid when you need frequent insertions/deletions in the middle; prefer `LinkedList<T>` for such cases.
    

## SortedSet&lt;T&gt;

`SortedSet<T>` is a generic collection in the `System.Collections.Generic` namespace that maintains elements in **sorted order** automatically. It ensures:

1. **No duplicates**: Each element is unique.
    
2. **Sorted order**: Elements are sorted based on the default comparer or a custom comparer provided during initialization.
    

---

**Key Features**

1. **Automatically Sorted**: Elements are stored in ascending order by default.
    
2. **No Duplicates**: Prevents duplicate elements, like `HashSet<T>`.
    
3. **Custom Comparer Support**: You can define your sorting logic using an `IComparer<T>`.
    

---

**Examples**

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a SortedSet of integers
        SortedSet<int> numbers = new SortedSet<int>();

        // Add elements
        numbers.Add(30);
        numbers.Add(10);
        numbers.Add(20);
        numbers.Add(10); // Duplicate, won't be added

        // Display elements (sorted automatically)
        Console.WriteLine("Numbers in SortedSet:");
        foreach (var number in numbers)
        {
            Console.WriteLine(number); // Output: 10, 20, 30
        }

        // Check if an element exists
        Console.WriteLine($"Contains 20: {numbers.Contains(20)}"); // Output: True

        // Remove an element
        numbers.Remove(10);

        // Display updated set
        Console.WriteLine("\\nUpdated SortedSet:");
        foreach (var number in numbers)
        {
            Console.WriteLine(number); // Output: 20, 30
        }
    }
}
```

---

**Common Operations**

1. **Add an item**:
    
    ```csharp
    numbers.Add(40); // Adds 40, maintaining sorted order.
    ```
    
2. **Remove an item**:
    
    ```csharp
    numbers.Remove(30); // Removes 30 from the set.
    ```
    
3. **Check for an item**:
    
    ```csharp
    Console.WriteLine(numbers.Contains(20)); // Returns true if 20 exists.
    ```
    
4. **Find elements with conditions**:
    
    ```csharp
    // Find the smallest value greater than 15
    int minValue = numbers.GetViewBetween(15, int.MaxValue).Min;
    ```
    
5. **Union, Intersection, and Difference**:
    
    ```csharp
    SortedSet<int> otherSet = new SortedSet<int> { 20, 40, 50 };
    numbers.UnionWith(otherSet);       // Combines both sets
    numbers.IntersectWith(otherSet);   // Keeps common elements
    numbers.ExceptWith(otherSet);      // Removes elements present in `otherSet`
    ```
    

---

**When to use SortedSet&lt;T&gt;?**

* **Automatic sorting**: Use when you need elements to remain sorted automatically.
    
* **No duplicates**: Ideal for collections where duplicate elements are not allowed.
    
* **Sorted operations**: Suitable when you often need sorted views or ranges.
    

## Dictionary&lt;TKey, TValue&gt;

A `Dictionary<TKey, TValue>` is a generic collection that stores **key-value pairs**. It allows for **fast lookups, additions, and deletions** based on unique keys.

---

**Key Features**

1. **Fast Access**: Uses a hash table for fast retrieval by key.
    
2. **Unique Keys**: Each key must be unique.
    
3. **Unordered**: The order of elements is not guaranteed.
    

---

**Examples**

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a Dictionary
        Dictionary<int, string> students = new Dictionary<int, string>();

        // Add key-value pairs
        students.Add(1, "Alice");
        students.Add(2, "Bob");
        students.Add(3, "Charlie");

        // Access a value by key
        Console.WriteLine($"Student with ID 2: {students[2]}"); // Output: Bob

        // Update a value
        students[2] = "Robert";

        // Remove a key-value pair
        students.Remove(1);

        // Iterate through the Dictionary
        Console.WriteLine("\\nRemaining Students:");
        foreach (var kvp in students)
        {
            Console.WriteLine($"ID: {kvp.Key}, Name: {kvp.Value}");
        }
    }
}
```

```csharp
Student with ID 2: Bob

Remaining Students:
ID: 2, Name: Robert
ID: 3, Name: Charlie
```

---

## SortedDictionary&lt;TKey, TValue&gt;

A `SortedDictionary<TKey, TValue>` is similar to a `Dictionary<TKey, TValue>` but **keeps the elements sorted by their keys**.

---

**Key Features**

1. **Automatically Sorted**: Keys are stored in ascending order.
    
2. **Binary Search Tree**: Internally uses a red-black tree for operations.
    
3. **Unique Keys**: Each key must be unique, just like a regular dictionary.
    

---

**Examples**

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a SortedDictionary
        SortedDictionary<int, string> students = new SortedDictionary<int, string>();

        // Add key-value pairs
        students.Add(3, "Charlie");
        students.Add(1, "Alice");
        students.Add(2, "Bob");

        // Iterate through the SortedDictionary
        Console.WriteLine("Students in SortedDictionary:");
        foreach (var kvp in students)
        {
            Console.WriteLine($"ID: {kvp.Key}, Name: {kvp.Value}");
        }
    }
}
```

**Output:**

```csharp
Students in SortedDictionary:
ID: 1, Name: Alice
ID: 2, Name: Bob
ID: 3, Name: Charlie
```

---

### **Comparison of** `Dictionary` vs `SortedDictionary`

| Feature | Dictionary | SortedDictionary |
| --- | --- | --- |
| **Order of Keys** | Unordered | Sorted in ascending order |
| **Performance** | Faster for most operations (O(1)) | Slower (O(log n)) due to sorting |
| **Best Use Case** | Fast lookups and updates | When sorted keys are needed |

---

**When to use which?**

1. **Use** `Dictionary<TKey, TValue>`:
    
    * When key order doesn't matter.
        
    * For maximum performance in lookups, additions, and removals.
        
2. **Use** `SortedDictionary<TKey, TValue>`:
    
    * When you need the keys sorted.
        
    * For scenarios requiring range-based queries or ordered traversal.
        

## Stack&lt;T&gt;

A `Stack<T>` is a generic collection in the `System.Collections.Generic` namespace that follows the **LIFO (Last In, First Out)** principle. This means the last element added to the stack is the first one to be removed.

---

**Key Features**

1. **LIFO Behavior**: Operates like a stack of plates—remove the top one first.
    
2. **Fast Operations**: Adding and removing items (via `Push` and `Pop`) are O(1).
    
3. **Use Case**: Suitable for scenarios like backtracking, undo functionality, or expression evaluation.
    

---

**Examples**

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a Stack
        Stack<string> books = new Stack<string>();

        // Add items to the stack (Push)
        books.Push("The Catcher in the Rye");
        books.Push("To Kill a Mockingbird");
        books.Push("1984");

        // Display the top item
        Console.WriteLine($"Top book: {books.Peek()}"); // Output: 1984

        // Remove the top item (Pop)
        Console.WriteLine($"Removed book: {books.Pop()}"); // Output: 1984

        // Display remaining items
        Console.WriteLine("\\nRemaining Books:");
        foreach (var book in books)
        {
            Console.WriteLine(book);
        }
    }
}
```

**Output:**

```csharp
Top book: 1984
Removed book: 1984

Remaining Books:
To Kill a Mockingbird
The Catcher in the Rye
```

---

## Queue&lt;T&gt;

A `Queue<T>` is a generic collection in the `System.Collections.Generic` namespace that follows the **FIFO (First In, First Out)** principle. This means the first element added to the queue is the first one to be removed.

---

**Key Features**

1. **FIFO Behavior**: Works like a queue in real life—serve the first person in line first.
    
2. **Fast Operations**: Adding (`Enqueue`) and removing (`Dequeue`) items are O(1).
    
3. **Use Case**: Suitable for scenarios like task scheduling, printer spooling, or breadth-first search.
    

---

**Example**

```csharp
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Create a Queue
        Queue<string> customers = new Queue<string>();

        // Add items to the queue (Enqueue)
        customers.Enqueue("Alice");
        customers.Enqueue("Bob");
        customers.Enqueue("Charlie");

        // Display the front item
        Console.WriteLine($"First customer: {customers.Peek()}"); // Output: Alice

        // Remove the front item (Dequeue)
        Console.WriteLine($"Served customer: {customers.Dequeue()}"); // Output: Alice

        // Display remaining items
        Console.WriteLine("\\nRemaining Customers:");
        foreach (var customer in customers)
        {
            Console.WriteLine(customer);
        }
    }
}
```

**Output:**

```csharp
First customer: Alice
Served customer: Alice

Remaining Customers:
Bob
Charlie
```

---

### **Comparison of** `Stack<T>` vs `Queue<T>`

| Feature | Stack (LIFO) | Queue (FIFO) |
| --- | --- | --- |
| **Order of Operations** | Last In, First Out | First In, First Out |
| **Main Methods** | `Push`, `Pop`, `Peek` | `Enqueue`, `Dequeue`, `Peek` |
| **Use Case Examples** | Undo functionality, backtracking | Task scheduling, breadth-first search |

---

### **When to Use Which?**

1. **Use** `Stack<T>`:
    
    * When the most recently added item is needed first.
        
    * For operations like undo, parsing expressions, or function call tracking.
        
2. **Use** `Queue<T>`:
    
    * When the first item added should be processed first.
        
    * For scenarios like processing requests, managing tasks, or event handling.
