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

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<T>
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
No duplicates: Prevents adding the same element multiple times.
Unordered: Elements are stored without a specific sequence.
Fast operations: Adding, removing, and searching are very fast (average O(1) time complexity).
Examples
Here’s a short example to understand its usage:
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
Check for an item:
Console.WriteLine(fruits.Contains("Banana")); // Output: TrueRemove an item:
fruits.Remove("Apple");Perform set operations (like union and intersection):
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<T>
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
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.
Doubly Linked: Each node points to both the previous and next nodes, making traversal bidirectional.
Individually Allocated Nodes: Each element is stored as a separate object, which can increase memory overhead.
Performance Considerations
Strengths:
Best for frequent insertions or deletions at the middle of the list.
No need to copy the entire collection during modifications.
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
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<T>
List<T> is a generic collection in the System.Collections.Generic namespace. It represents a resizable array that allows:
Indexed Access: Elements can be accessed using an index.
Dynamic Resizing: Automatically resizes when adding elements.
Versatility: Can hold any type of data, including custom objects.
Key Features
Index-Based Access: Quickly access elements using an index.
Dynamic: No need to specify a fixed size; it grows/shrinks as needed.
Methods for Manipulation: Includes methods like
Add,Remove,Sort, andFind.
Example
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
Add an item:
numbers.Add(40); // Adds 40 to the end of the list.Remove an item:
numbers.Remove(10); // Removes the first occurrence of 10.Find an item:
int found = numbers.Find(x => x > 20); // Finds the first element > 20.Access by index:
int first = numbers[0]; // Accesses the first element.Sort the list:
numbers.Sort(); // Sorts the list in ascending order.
When to use List<T>?
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<T>
SortedSet<T> is a generic collection in the System.Collections.Generic namespace that maintains elements in sorted order automatically. It ensures:
No duplicates: Each element is unique.
Sorted order: Elements are sorted based on the default comparer or a custom comparer provided during initialization.
Key Features
Automatically Sorted: Elements are stored in ascending order by default.
No Duplicates: Prevents duplicate elements, like
HashSet<T>.Custom Comparer Support: You can define your sorting logic using an
IComparer<T>.
Examples
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
Add an item:
numbers.Add(40); // Adds 40, maintaining sorted order.Remove an item:
numbers.Remove(30); // Removes 30 from the set.Check for an item:
Console.WriteLine(numbers.Contains(20)); // Returns true if 20 exists.Find elements with conditions:
// Find the smallest value greater than 15 int minValue = numbers.GetViewBetween(15, int.MaxValue).Min;Union, Intersection, and Difference:
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<T>?
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<TKey, TValue>
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
Fast Access: Uses a hash table for fast retrieval by key.
Unique Keys: Each key must be unique.
Unordered: The order of elements is not guaranteed.
Examples
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}");
}
}
}
Student with ID 2: Bob
Remaining Students:
ID: 2, Name: Robert
ID: 3, Name: Charlie
SortedDictionary<TKey, TValue>
A SortedDictionary<TKey, TValue> is similar to a Dictionary<TKey, TValue> but keeps the elements sorted by their keys.
Key Features
Automatically Sorted: Keys are stored in ascending order.
Binary Search Tree: Internally uses a red-black tree for operations.
Unique Keys: Each key must be unique, just like a regular dictionary.
Examples
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:
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?
Use
Dictionary<TKey, TValue>:When key order doesn't matter.
For maximum performance in lookups, additions, and removals.
Use
SortedDictionary<TKey, TValue>:When you need the keys sorted.
For scenarios requiring range-based queries or ordered traversal.
Stack<T>
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
LIFO Behavior: Operates like a stack of plates—remove the top one first.
Fast Operations: Adding and removing items (via
PushandPop) are O(1).Use Case: Suitable for scenarios like backtracking, undo functionality, or expression evaluation.
Examples
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:
Top book: 1984
Removed book: 1984
Remaining Books:
To Kill a Mockingbird
The Catcher in the Rye
Queue<T>
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
FIFO Behavior: Works like a queue in real life—serve the first person in line first.
Fast Operations: Adding (
Enqueue) and removing (Dequeue) items are O(1).Use Case: Suitable for scenarios like task scheduling, printer spooling, or breadth-first search.
Example
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:
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?
Use
Stack<T>:When the most recently added item is needed first.
For operations like undo, parsing expressions, or function call tracking.
Use
Queue<T>:When the first item added should be processed first.
For scenarios like processing requests, managing tasks, or event handling.