C# Array Initialization: Syntax and Common Patterns
c# array initialization: Learn the common ways to initialize arrays in C#, including new, collection expressions, multi-dimensional and jagged arrays, and when to choo...
c# array initialization requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you need to store a fixed number of elements in C#, the array is the most direct type. The way you initialize an array affects readability, memory usage, and how easy the code is to maintain. This article covers the common initialization forms, their runtime behavior, and when to choose an array over a List.
The Basic Array Initialization Forms
In C#, you can initialize an array in several ways. The most common are:
int[] numbers = new int[5]; // creates an array of 5 ints, all set to 0 int[] explicitValues = new int[] { 1, 2, 3, 4, 5 }; int[] shorthand = { 1, 2, 3, 4, 5 }; var inferred = new[] { 1, 2, 3, 4, 5 };
The first form allocates an array of the specified length and fills each element with the default value for the element type. For int, that default is 0. For reference types like string, it's null. The second and third forms are equivalent; the third is a shorthand that works only when you declare the variable and assign the array in the same statement. The var form lets the compiler infer the element type from the initializer values.
Default Values and Memory Behavior
When you create an array with a fixed length but no initializer, every element is set to the type's default value. For value types, this is the result of default(T). For reference types, it's null. This is true for arrays of any rank. For example:
string[] names = new string[3]; // all null bool[] flags = new bool[2]; // all false
The array itself is a reference type, so the variable holds a reference to a contiguous block of memory on the managed heap. The size of that block is the element size multiplied by the length, plus a small header. This means a large array can cause a OutOfMemoryException if the allocation exceeds the available memory or the array length limit (which is int.MaxValue for single-dimensional arrays in most runtimes). For most applications, this is not a practical concern, but it explains why arrays are not suitable for extremely large collections that need to grow dynamically.
Initializing Multi-Dimensional and Jagged Arrays
C# supports two distinct kinds of multi-dimensional arrays: rectangular and jagged.
A rectangular array has a fixed number of rows and columns, and all rows have the same length. You initialize it like this:
int[,] matrix = new int[3, 4]; // 3 rows, 4 columns, all zeros int[,] explicitMatrix = new int[,] { { 1, 2 }, { 3, 4 } };
The first form creates a 3×4 matrix with default values. The second form uses nested initializers to set values directly.
A jagged array is an array of arrays. Each inner array can have a different length. Initialization is more verbose because you must create each inner array separately:
int[][] jagged = new int[3][]; jagged[0] = new int[] { 1, 2 }; jagged[1] = new int[] { 3, 4, 5 }; jagged[2] = new int[] { 6 };
You can also initialize a jagged array inline:
int[][] jagged = new int[][] { new int[] { 1, 2 }, new int[] { 3, 4, 5 } };
Jagged arrays are often more memory-efficient than rectangular arrays when row lengths vary, because you allocate only the space you actually need. However, they have an extra level of indirection when accessing elements, which can affect performance in tight loops.
Collection Expressions in Modern C#
Starting with C# 12, you can use collection expressions to initialize arrays in a more concise way. The syntax uses square brackets directly:
int[] numbers = [1, 2, 3, 4, 5];
This is equivalent to the older new int[] { 1, 2, 3, 4, 5 } form. Collection expressions also work for multi-dimensional and jagged arrays, though the syntax for jagged arrays requires nested brackets:
int[][] jagged = [[1, 2], [3, 4, 5]];
Collection expressions are a compile-time feature that produces the same IL as the explicit forms. They are purely a readability improvement. If you work in a codebase that targets an older C# version, you cannot use this syntax; you'll need to fall back to the traditional initializers.
Common Pitfalls and Edge Cases
Array covariance is a subtle behavior that can cause runtime exceptions. In C#, an array of a derived type can be assigned to an array of a base type. For example:
object[] objects = new string[3]; objects[0] = 1; // throws ArrayTypeMismatchException at runtime
The assignment objects[0] = 1 fails because the underlying array is actually a string[]. This is a legacy feature that can mask type errors until runtime. Prefer List<T> or generic collections when you need type safety that the compiler can enforce.
Another edge case is initializing an array with a single element. The syntax int[] arr = { 1 }; works, but be careful not to confuse it with a collection initializer that uses parentheses.
Empty arrays are also worth handling explicitly. An array with zero length is valid and has a non-null reference. You can create one with Array.Empty<T>() to avoid allocating a new empty array each time:
int[] empty = Array.Empty<int>();
Array.Empty<T>() returns a cached instance, which is more efficient than new int[0] when you create many empty arrays.
Performance and Memory Considerations
Array initialization has a direct cost: the runtime must allocate the contiguous block and, for value types, zero out every element. This is a linear operation in the array length. For large arrays, this can be noticeable. If you need to build a collection incrementally, a List<T> may be a better choice because it amortizes resizing and avoids a single large allocation upfront.
However, arrays have a performance advantage when you need indexed access and know the size in advance. They have minimal overhead and are the underlying storage for many other collection types. The JIT can also optimize bounds checks in some loops, making array iteration very fast.
When you initialize an array with values, the compiler emits code that stores each literal into the array at runtime. There's no special compile-time optimization that avoids the writes. So the cost is proportional to the number of elements.
Choosing Between Array and List for Initialization
The decision between an array and a List<T> often comes down to whether the collection size is fixed and known at compile time. If you know the exact number of elements and that number will not change, an array is a good fit. If the collection needs to grow or shrink, a List<T> is more practical.
For example, when you read data from a file and the number of lines is unknown, a List<string> is easier to populate:
List<string> lines = new List<string>(); while (reader.ReadLine() is string line) { lines.Add(line); }
An array would require you to know the length in advance or use a temporary list and then convert with ToArray(). That conversion copies the elements, adding overhead.
In performance-sensitive code, arrays are often preferred for fixed-size buffers, such as when working with Span<T> or interop. For general application code, List<T> is more flexible and still offers fast indexed access.
The key is to match the collection type to the actual usage pattern. If the size is fixed and the data is homogeneous, use an array. If the size varies or you need to insert or remove elements, use a List<T>.