C# Arrays and Collections — Arrays, List, Dictionary, HashSet, and Collection Initializers
In this tutorial, you will learn about C# Arrays and Collections. We cover key concepts, practical examples, and best practices to help you master this topic.
C# collections are data structures for storing and organizing multiple values, with arrays for fixed-size storage, List
What You'll Learn
You will master collections in C#: arrays for fixed-size contiguous storage, List<T> for dynamic resizable collections, Dictionary<TKey, TValue> for fast key-based lookup, HashSet<T> for unique element sets, collection initializers, and how these collections integrate with .NET LINQ and the type system.
Why It Matters
Choosing the right collection type directly affects application performance and code clarity. Using a List when you need Dictionary-level lookups causes O(n) performance instead of O(1). Using an array when you need dynamic sizing requires manual resizing. Understanding collection characteristics is essential for writing efficient C# code.
Real-World Use
Web applications use Dictionary for HTTP header collections and route parameters. E-commerce systems use HashSet for tracking unique product IDs in shopping carts. Data processing uses List
Learning Path
graph LR
A["17: Strings"] --> B["18: Arrays & Collections"]
B --> C["19: Generics"]
C --> D["20: Exception Handling"]
D --> E["21: LINQ"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Arrays
Fixed-size, contiguous memory storage:
// Declaration and initialization
int[] numbers = new int[5]; // All zeros
int[] primes = new int[] { 2, 3, 5, 7, 11 };
int[] squares = { 1, 4, 9, 16, 25 }; // Shorthand
// Accessing elements
Console.WriteLine(primes[0]); // 2
primes[0] = 42; // Modify
// Length (fixed)
Console.WriteLine(primes.Length); // 5
// Iteration
for (int i = 0; i < primes.Length; i++)
Console.Write($"{primes[i]} ");
Console.WriteLine();
foreach (int p in primes)
Console.Write($"{p} ");
// Multidimensional arrays
int[,] matrix = new int[3, 3];
matrix[0, 0] = 1;
int[][] jagged = new int[3][]; // Array of arrays
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5 };
// Array methods
int[] copy = new int[5];
Array.Copy(primes, copy, 3); // Copy first 3 elements
Array.Sort(primes); // Sort in place
int index = Array.IndexOf(primes, 7); // Find index
Array.Reverse(primes); // Reverse in place
Array.Fill(numbers, -1); // Fill all elements
List
Dynamic, resizable collection:
// Creating lists
List<int> numbers = new List<int>();
var names = new List<string> { "Alice", "Bob", "Charlie" };
var capacity = new List<int>(100); // Pre-allocate capacity
// Adding elements
numbers.Add(10);
numbers.AddRange(new[] { 20, 30, 40 });
numbers.Insert(1, 15); // Insert at index
// Accessing
Console.WriteLine(numbers[0]); // 10
Console.WriteLine(numbers.Count); // 5
// Removing
numbers.Remove(15); // Remove by value
numbers.RemoveAt(0); // Remove by index
numbers.RemoveAll(n => n > 30); // Remove by condition
// Searching
bool exists = numbers.Contains(20);
int index = numbers.IndexOf(20);
// Sorting and conversion
numbers.Sort();
int[] array = numbers.ToArray();
// List-specific operations
var list = new List<int> { 1, 2, 3, 4, 5 };
list.Reverse();
Console.WriteLine(string.Join(", ", list)); // 5, 4, 3, 2, 1
var slice = list.GetRange(1, 3); // Index 1, count 3
Console.WriteLine(string.Join(", ", slice)); // 4, 3, 2
Dictionary<TKey, TValue>
Fast key-value lookups:
// Creating dictionaries
var scores = new Dictionary<string, int>();
var config = new Dictionary<string, string>
{
["ServerUrl"] = "https://api.example.com",
["Timeout"] = "30",
["RetryCount"] = "3"
};
// Adding entries
scores.Add("Alice", 95);
scores["Bob"] = 87; // Same as Add if key doesn't exist
scores["Charlie"] = 92;
// Accessing (safe)
if (scores.TryGetValue("Alice", out int aliceScore))
{
Console.WriteLine($"Alice: {aliceScore}");
}
// Checking existence
bool hasKey = scores.ContainsKey("David");
bool hasValue = scores.ContainsValue(95); // Slow for large dictionaries
// Iteration
foreach (KeyValuePair<string, int> kvp in scores)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
foreach (var key in scores.Keys)
Console.WriteLine(key);
foreach (var value in scores.Values)
Console.WriteLine(value);
// Removing
scores.Remove("Charlie");
// Default value pattern
int davidScore = scores.GetValueOrDefault("David", 0);
Console.WriteLine(davidScore); // 0
HashSet
Unique elements with fast set operations:
var unique = new HashSet<int> { 1, 2, 3, 3, 2, 1 };
Console.WriteLine(string.Join(", ", unique)); // 1, 2, 3
// Adding
unique.Add(4);
bool added = unique.Add(4); // False (already exists)
// Set operations
var setA = new HashSet<int> { 1, 2, 3, 4 };
var setB = new HashSet<int> { 3, 4, 5, 6 };
setA.IntersectWith(setB); // { 3, 4 }
setA.UnionWith(setB); // { 1, 2, 3, 4, 5, 6 }
setA.ExceptWith(setB); // { 1, 2 }
setA.SymmetricExceptWith(setB); // Elements in one but not both
// Checking
bool contains = setA.Contains(3);
bool isSubset = setA.IsSubsetOf(setB);
bool overlaps = setA.Overlaps(setB);
Collection Initializers
// List
List<int> list = new List<int> { 1, 2, 3 };
// Dictionary
var dict = new Dictionary<int, string>
{
{ 1, "one" },
{ 2, "two" },
{ 3, "three" }
};
// Or with index initializers (C# 6+)
var dict2 = new Dictionary<int, string>
{
[1] = "one",
[2] = "two",
};
// HashSet
var set = new HashSet<string> { "apple", "banana", "cherry" };
// Custom collection initializer
public class Team
{
public List<string> Members { get; } = new();
public void Add(string member) => Members.Add(member);
}
var team = new Team { "Alice", "Bob", "Charlie" };
Collection Performance
| Collection | Access | Search | Add | Remove | Memory |
|---|---|---|---|---|---|
| Array | O(1) | O(n) | O(1)* | O(n) | Low |
| List |
O(1) | O(n) | O(1)** | O(n) | Low |
| Dictionary |
- | O(1) | O(1) | O(1) | Medium |
| HashSet |
- | O(1) | O(1) | O(1) | Medium |
| Stack |
- | O(n) | O(1) | O(1) | Low |
| Queue |
- | O(n) | O(1) | O(1) | Low |
*Array size is fixed; adding beyond capacity requires new allocation **Amortized O(1); O(n) when internal array needs resizing
Choosing the Right Collection
// Need sequential access by index? Use array or List<T>
var users = new List<User>(LoadUsers());
// Need fast key lookup? Use Dictionary
var userById = new Dictionary<int, User>();
foreach (var user in users)
userById[user.Id] = user;
// Need unique items with fast lookup? Use HashSet
var processedIds = new HashSet<int>();
// Need LIFO order? Use Stack<T>
var navigation = new Stack<string>();
navigation.Push("Page1");
navigation.Push("Page2");
string back = navigation.Pop(); // Page2
// Need FIFO order? Use Queue<T>
var queue = new Queue<Task>();
queue.Enqueue(new Task("Task1"));
queue.Enqueue(new Task("Task2"));
Task next = queue.Dequeue(); // Task1
// Need sorted key-value pairs? Use SortedDictionary or SortedList
var sorted = new SortedDictionary<string, int>();
Common Mistakes
Mistake 1: Using List When Dictionary Is Appropriate
Linear search in a List is O(n). If you frequently look up by a key, use Dictionary for O(1) lookups.
Mistake 2: Not Pre-Allocating Capacity
new List<int>(100000) allocates the internal array once. Without capacity, the list resizes multiple times, causing unnecessary allocations and copies.
Mistake 3: Modifying Collections During Enumeration
Adding or removing items in a foreach loop throws InvalidOperationException. Collect changes in a separate list, then apply after iteration.
Mistake 4: Using Array of the Wrong Size
Arrays have fixed size. If you need dynamic sizing, use ListArray.Resize is inefficient.
Mistake 5: Ignoring Dictionary Key Equality
Dictionary keys use the default equality comparer. For custom types, override Equals and GetHashCode, or provide an IEqualityComparer<T>.
Mistake 6: Confusing Count and Capacity
List.Capacity is the internal array size. List.Count is the actual number of elements. Capacity >= Count.
Practice Questions
- When would you use a Dictionary<string, T> instead of a List
? - What is the difference between an array and a List
? - How does HashSet
ensure element uniqueness? - Why would you pre-allocate capacity in a List
? - Write code to count word frequencies in a string using Dictionary<string, int>.
Challenge
Implement a simple in-memory cache using Dictionary<string, (object Value, DateTime ExpiresAt)> that supports expiration. Add methods for Get, Set with TTL, and automatic cleanup of expired entries.
FAQ
Mini Project
Create an in-memory data store with indexing:
public class InMemoryStore<T>
{
private readonly Dictionary<int, T> _items = new();
private readonly Dictionary<string, Dictionary<object, List<int>>> _indexes = new();
private int _nextId = 1;
public int Insert(T item)
{
int id = _nextId++;
_items[id] = item;
UpdateIndexes(id, item);
return id;
}
public T? Get(int id) => _items.GetValueOrDefault(id);
public List<T> Query(string fieldName, object value)
{
if (!_indexes.TryGetValue(fieldName, out var fieldIndex))
return new List<T>();
if (!fieldIndex.TryGetValue(value, out var ids))
return new List<T>();
return ids.Select(id => _items[id]).ToList();
}
private void UpdateIndexes(int id, T item)
{
foreach (var prop in typeof(T).GetProperties())
{
var value = prop.GetValue(item);
if (value == null) continue;
if (!_indexes.ContainsKey(prop.Name))
_indexes[prop.Name] = new Dictionary<object, List<int>>();
var fieldIndex = _indexes[prop.Name];
if (!fieldIndex.ContainsKey(value))
fieldIndex[value] = new List<int>();
fieldIndex[value].Add(id);
}
}
}
var store = new InMemoryStore<Dictionary<string, object>>();
var alice = new Dictionary<string, object> { { "Name", "Alice" }, { "City", "NYC" }, { "Age", 30 } };
var bob = new Dictionary<string, object> { { "Name", "Bob" }, { "City", "LA" }, { "Age", 25 } };
var charlie = new Dictionary<string, object> { { "Name", "Charlie" }, { "City", "NYC" }, { "Age", 35 } };
store.Insert(alice);
store.Insert(bob);
store.Insert(charlie);
var nycUsers = store.Query("City", "NYC");
Console.WriteLine("Users in NYC:");
foreach (var user in nycUsers)
Console.WriteLine($" {user["Name"]}, Age: {user["Age"]}");
Expected output:
Users in NYC:
Alice, Age: 30
Charlie, Age: 35
What's Next
You have mastered arrays and collections in C#. The next lesson covers generics: type parameters, constraints, covariance, and contravariance.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro