Collections
Collections are used to store, manage, and manipulate groups of objects.
In C#, collections can be categorized into two major types:
- Non-generic collections: These are collections that store objects of any type.
- Generic collections: These are collections that store objects of a specific type, providing type safety and performance improvements.
Non-generic Collections
Before the introduction of generics in C# 2.0, all collections were based on the System.Collections namespace, and they stored elements as object.
Key Types:
- ArrayList: Stores a dynamically sized collection of objects.
- Hashtable: A collection of key-value pairs, but keys and values are of type object.
- Queue / Stack: Used for FIFO (First In First Out) and LIFO (Last In First Out) operations.
ArrayList list = new ArrayList();
list.Add(1);
list.Add("two");
list.Add(3.0);
Generic Collections
Generics provide type safety, performance improvements, and avoid the need for casting when working with collections.
Generic collections are found in the System.Collections.Generic namespace.
Key Types (from System.Collections.Generic):
- List
: A dynamically sized collection that stores elements of type T. - Dictionary<TKey, TValue>: A collection of key-value pairs, both key and value are of type T.
- Queue
/ Stack : Generic versions of Queue and Stack, providing type safety.
Example of List
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(numbers[0]); // 10
Console.WriteLine(numbers[1]); // 20
Console.WriteLine($"Count: {numbers.Count}"); // Count: 3
foreach (int num in numbers)
{
Console.WriteLine(num);
}
}
}
important method in list:
List<string> fruits = new List<string>();
// Add
fruits.Add("Apple");
fruits.Add("Banana");
fruits.Add("Orange");
// Insert
fruits.Insert(1, "Mango"); // ["Apple", "Mango", "Banana", "Orange"]
// Remove
fruits.Remove("Banana"); // ["Apple", "Mango", "Orange"]
// RemoveAt
fruits.RemoveAt(0); // ["Mango", "Orange"]
// Contains
bool hasApple = fruits.Contains("Apple"); // false
// Clear
fruits.Clear(); // []
Example of Dictionary<TKey, TValue>
Dictionary<string, string> countries = new Dictionary<string, string>();
countries.Add("IR", "Iran");
countries.Add("US", "United States");
countries.Add("TR", "Turkey");
Console.WriteLine(countries["IR"]); // Iran
if (countries.ContainsKey("US"))
{
Console.WriteLine("US exists");
}
foreach (KeyValuePair<string, string> item in countries)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
Example of HashSet
HashSet<int> uniqueNumbers = new HashSet<int>();
uniqueNumbers.Add(1);
uniqueNumbers.Add(2);
uniqueNumbers.Add(1); // ❌ duplicate- cannot be added
Console.WriteLine(string.Join(", ", uniqueNumbers)); // 1, 2
Example of Queue - (First In First Out)
Queue<string> queue = new Queue<string>();
queue.Enqueue("First");
queue.Enqueue("Second");
queue.Enqueue("Third");
Console.WriteLine(queue.Dequeue()); // First
Console.WriteLine(queue.Dequeue()); // Second
Example of Stack - (Last In First Out)
Stack<string> stack = new Stack<string>();
stack.Push("First");
stack.Push("Second");
stack.Push("Third");
Console.WriteLine(stack.Pop()); // Third
Console.WriteLine(stack.Pop()); // Second
Immutable Collections
Immutable collections are collections that cannot be modified after they are created.
They are part of the System.Collections.Immutable namespace.
Key Types:
- ImmutableList
: An immutable version of List . - ImmutableDictionary<TKey, TValue>: An immutable version of Dictionary<TKey, TValue>
using System.Collections.Immutable;
var immutableList = ImmutableList<int>.Empty.Add(1).Add(2).Add(3);
Console.WriteLine(string.Join(", ", immutableList)); // 1, 2, 3
// Cannot make changes
// immutableList.Add(4); // This line will throw an error because it's immutable.
Concurrent Collections
Concurrent collections were introduced to support thread-safe operations for multi-threaded applications.
Key Types:
- ConcurrentQueue
: A thread-safe version of Queue . - ConcurrentStack
: A thread-safe version of Stack . - ConcurrentDictionary<TKey, TValue>: A thread-safe version of Dictionary<TKey, TValue>
using System.Collections.Concurrent;
ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
queue.Enqueue("First");
queue.Enqueue("Second");
Console.WriteLine(queue.Dequeue()); // First
Custom Collections
If you need a specific collection, you can create your own custom collections. For this purpose, you can implement IEnumerable
public class MyCollection<T> : ICollection<T>
{
private List<T> _items = new List<T>();
public int Count => _items.Count;
public bool IsReadOnly => false;
public void Add(T item) => _items.Add(item);
public void Clear() => _items.Clear();
public bool Contains(T item) => _items.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
public bool Remove(T item) => _items.Remove(item);
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
}
How to Choose the Right Collection?
-
List
: When you need indexed access and care about maintaining order. -
Dictionary<TKey, TValue>: When you need fast lookups by key.
-
HashSet
: When uniqueness of elements is important. -
Queue
: When you need first-in, first-out (FIFO) processing. -
Stack
: When you need last-in, first-out (LIFO) behavior, such as undo/redo operations. -
Immutable Collections: When thread-safety and predictability are important.
-
Concurrent Collections: When working in multi-threaded environments.