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>
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()
{
HashSet<string> fruits = new HashSet<string>();
fruits.Add("Apple");
fruits.Add("Banana");
fruits.Add("Apple");
Console.WriteLine("Fruits in HashSet:");
foreach (var fruit in fruits)
{
Console.WriteLine(fruit);
}
}
}
Common operations
Check for an item:
Console.WriteLine(fruits.Contains("Banana"));
Remove an item:
fruits.Remove("Apple");
Perform set operations (like union and intersection):
HashSet<string> moreFruits = new HashSet<string> { "Banana", "Cherry" };
fruits.UnionWith(moreFruits);
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:
Weaknesses:
Examples
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
LinkedList<string> animals = new LinkedList<string>();
animals.AddLast("Dog");
animals.AddLast("Cat");
animals.AddFirst("Bird");
Console.WriteLine("Animals in LinkedList:");
foreach (var animal in animals)
{
Console.WriteLine(animal);
}
LinkedListNode<string> dogNode = animals.Find("Dog");
if (dogNode != null)
{
animals.AddAfter(dogNode, "Rabbit");
}
animals.Remove("Cat");
Console.WriteLine("\\nUpdated LinkedList:");
foreach (var animal in animals)
{
Console.WriteLine(animal);
}
}
}
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, and Find.
Example
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> numbers = new List<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);
Console.WriteLine($"First element: {numbers[0]}");
numbers.Insert(1, 15);
numbers.Remove(20);
Console.WriteLine("Numbers in List:");
foreach (var number in numbers)
{
Console.WriteLine(number);
}
numbers.Sort();
Console.WriteLine("\\nSorted List:");
foreach (var number in numbers)
{
Console.WriteLine(number);
}
}
}
Common Operations
Add an item:
numbers.Add(40);
Remove an item:
numbers.Remove(10);
Find an item:
int found = numbers.Find(x => x > 20);
Access by index:
int first = numbers[0];
Sort the list:
numbers.Sort();
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()
{
SortedSet<int> numbers = new SortedSet<int>();
numbers.Add(30);
numbers.Add(10);
numbers.Add(20);
numbers.Add(10);
Console.WriteLine("Numbers in SortedSet:");
foreach (var number in numbers)
{
Console.WriteLine(number);
}
Console.WriteLine($"Contains 20: {numbers.Contains(20)}");
numbers.Remove(10);
Console.WriteLine("\\nUpdated SortedSet:");
foreach (var number in numbers)
{
Console.WriteLine(number);
}
}
}
Common Operations
Add an item:
numbers.Add(40);
Remove an item:
numbers.Remove(30);
Check for an item:
Console.WriteLine(numbers.Contains(20));
Find elements with conditions:
int minValue = numbers.GetViewBetween(15, int.MaxValue).Min;
Union, Intersection, and Difference:
SortedSet<int> otherSet = new SortedSet<int> { 20, 40, 50 };
numbers.UnionWith(otherSet);
numbers.IntersectWith(otherSet);
numbers.ExceptWith(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()
{
Dictionary<int, string> students = new Dictionary<int, string>();
students.Add(1, "Alice");
students.Add(2, "Bob");
students.Add(3, "Charlie");
Console.WriteLine($"Student with ID 2: {students[2]}");
students[2] = "Robert";
students.Remove(1);
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()
{
SortedDictionary<int, string> students = new SortedDictionary<int, string>();
students.Add(3, "Charlie");
students.Add(1, "Alice");
students.Add(2, "Bob");
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>:
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 Push and Pop) 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()
{
Stack<string> books = new Stack<string>();
books.Push("The Catcher in the Rye");
books.Push("To Kill a Mockingbird");
books.Push("1984");
Console.WriteLine($"Top book: {books.Peek()}");
Console.WriteLine($"Removed book: {books.Pop()}");
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()
{
Queue<string> customers = new Queue<string>();
customers.Enqueue("Alice");
customers.Enqueue("Bob");
customers.Enqueue("Charlie");
Console.WriteLine($"First customer: {customers.Peek()}");
Console.WriteLine($"Served customer: {customers.Dequeue()}");
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.