Collections and Arrays in C#
A C# array is not a "list" or a "buffer that grows itself". It is a fixed-size, reference-type object on the heap, where every element has one type, indexing starts at zero, and every access is bounds-checked. Almost every "magic" beginner error with collections is a wrong model of where the data lives (stack or heap), whether the size can change, and whether value types get boxed into object.
The key split that runs through the whole topic: generic collections (Dictionary<TKey,TValue>, List<T>) store one known type with no boxing and compile-time checking, while the non-generic .NET 1.0 relics (Hashtable, ArrayList) store everything as object, boxing value types and requiring casts on read. Understanding this difference — together with how arrays are laid out, the IEnumerator enumeration protocol, and the role of anonymous types in LINQ — turns scattered facts into one coherent picture. The full map lives in the layers below.
Topic map
- Arrays — fixed size, a heap object even for
int[], bounds checking, whyArray.Resizeallocates a new array. - Multidimensional arrays — a rectangular
int[,]as a single contiguous block with equal-length rows,GetLength. - Jagged arrays —
int[][]as an array of independent row arrays of differing length; indirection versus cache locality. - Dictionary — a generic hash map,
GetHashCode/Equals, average O(1) lookup,TryGetValue. - Hashtable — a non-generic map over
object, boxing and casts, why it is a legacy type. - ArrayList — a growable non-generic list over
object; boxing value types and moving toList<T>. - IEnumerator — the
MoveNext/Current/Resetcursor,IEnumerable, and howforeachunfolds into this protocol. - Anonymous types —
new { ... }, an unnamed immutable type reachable only throughvar, mainly for LINQ projections.
Common traps
| Mistake | Consequence |
|---|---|
| Assuming an array grows after creation | An array gets confused with List<T>; Length is fixed at creation and never changes |
Thinking int[] is a value type on the stack | Even int[] is a heap reference object; assignment copies the reference, not the elements |
Expecting an out-of-range index to give default or wrap | Access is bounds-checked and throws IndexOutOfRangeException |
Treating Hashtable as type-safe like Dictionary | Hashtable stores object, boxes value keys/values, and needs casts |
Reaching for ArrayList in new code | The legacy container boxes values; the modern choice is generic List<T> |
Thinking a jagged int[][] is one block | It is an array of separate row objects; a forgotten null row throws NullReferenceException |
Mutating a collection during foreach | The enumerator throws InvalidOperationException |
| Believing anonymous-type properties are mutable | They are read-only; the type is reachable only through var and cannot be returned by name |
Interview relevance
Collections come up on almost every junior/middle C# interview, but the check is your memory model, not a list of methods. A candidate who explains why int[] lives on the heap and why ArrayList boxes an int into object immediately stands out from "well, they are lists".
Typical checks:
- An array = a fixed-size heap reference object with bounds checking.
- The difference between a rectangular
int[,]and a jaggedint[][]— memory layout and cache locality. - Non-generic
Hashtable/ArrayListversus genericDictionary/List<T>: boxing and type safety. - The
IEnumeratorprotocol and howforeachunfolds intoMoveNext/Current. - What an anonymous type is and where it fits (LINQ projections).
Common wrong answer: "Hashtable and Dictionary are the same thing, just different syntax." That opens the discussion of boxing value types into object, casts on read, and why the generic Dictionary is the modern default.