C# Collection Expression Spread Element: Syntax and Behavior
Use the `..` spread element in C# collection expressions to copy elements from an existing collection into arrays, lists, spans, and immutable arrays. Includes syntax, runtime behavior, allocation tradeoffs, and edge cases.
The C# collection expression spread element uses the .. prefix to copy elements from an existing collection into a new collection expression. It was introduced with collection expressions in C# 12 and removes the need for explicit loops or Concat calls when building a combined collection.
int[] first = [1, 2, 3]; int[] second = [0, ..first, 4]; Console.WriteLine(string.Join(", ", second)); // 0, 1, 2, 3, 4
What the Spread Element Does
The spread element appears inside a collection expression and expands an enumerable source into the resulting collection. The syntax is two dots followed by the source expression:
int[] source = [10, 20]; int[] combined = [..source, 30];
The compiler emits code that iterates source and appends each element to the result. The source can be an iterable collection, such as an array, List<T>, Span<T>, or ImmutableArray<T>.
Basic Usage with Arrays and Lists
Arrays and lists are the most common targets. The spread element works the same way for both:
List<string> names = ["ada", "grace"]; List<string> more = ["alan", ..names, "linus"];
Because the target is a List<string>, the compiler produces a list that starts with "alan", then copies "ada" and "grace" from names, then appends "linus".
The same expression can target an array:
string[] all = ["alan", ..names, "linus"];
The choice of target type affects how the compiler builds the result, which matters for allocation behavior.
How the Spread Element Behaves at Runtime
At runtime, each spread element enumerates its source and copies each element into the new collection. The compiler does not copy the source container as a whole; it reads the elements one at a time. This has a few consequences.
First, the source expression is evaluated when the collection expression is evaluated. If the source is a method call or a lazily computed sequence, that work happens at that point.
Second, the resulting collection is a new instance. For arrays and lists, changing the collection structure (adding, removing, or replacing entries) does not affect the source, and changing the source later does not change the result. For reference-type elements, the new collection contains the same references, so mutating a referenced object through one collection is visible through the other.
int[] source = [1, 2]; int[] copy = [..source]; source[0] = 99; Console.WriteLine(copy[0]); // 1
Choosing the Target Collection Type
The target type determines how the compiler constructs the result. For an array, the compiler must know the length before allocating, so it may need to buffer spread elements first. For a List<T>, the compiler can grow the list incrementally.
| Target type | Allocation behavior | Best fit |
|---|---|---|
T[] | Exact-size array when the length is known; temporary buffering may be required when it is not | Fixed-size result |
List<T> | Grows as elements are added; capacity may be estimated when source lengths are known | Dynamic result size |
Span<T> | Uses compiler-generated backing storage; the allocation behavior depends on the expression shape and target context | Short-lived temporary results |
ImmutableArray<T> | Builds through a temporary builder, then exposes an immutable array | Immutable data |
For a Span<T> target, the compiler emits code that creates a span over backing storage containing the copied elements. The exact allocation behavior depends on the expression shape; for short-lived locals, the compiler may use inline backing storage, but you should not assume a heap allocation is always avoided.
Common Mistakes and Edge Cases
Spreading a null source throws a NullReferenceException at runtime because the compiler emits code that enumerates the source. Guard against null sources when the value comes from user input or an external API.
int[]? maybeEmpty = null; int[] result = [..maybeEmpty]; // throws
An empty collection spreads without error and contributes no elements:
int[] empty = []; int[] result = [1, ..empty, 2]; // 1, 2
The spread element cannot be used outside a collection expression. It is not a general-purpose operator for concatenating variables in other contexts.
Performance and Allocation Considerations
Each spread element adds iteration cost proportional to the source size. When the target is an array, the compiler may need a temporary buffer to determine the final length, which adds allocation. For List<T> targets, repeated growth can cause multiple internal array resizes, although the compiler can often estimate capacity when the source length is known.
For hot paths, choose a target type that matches the expected lifetime. A Span<T> target can avoid a heap allocation when the compiler can provide inline backing storage, but that depends on the expression and target context. An ImmutableArray<T> target is appropriate when the result must be shared safely across threads.
There is no benchmark data here; the relevant point is the mechanism. If enumerating the source is already the bottleneck, the spread element does not remove that cost. It only removes the boilerplate around it.
Compatibility and Language Version Requirements
Collection expressions and the spread element require C# 12 or later. The language version is controlled by the project's LangVersion setting, and the compiler must support C# 12, such as the compiler included in .NET SDK 8.0 or later.
Runtime compatibility depends on the APIs used by the generated code. Span<T> targets require span runtime support, which means .NET Core 2.1 or later or the System.Memory package. ImmutableArray<T> targets require the System.Collections.Immutable package. For array and List<T> targets, confirm that the target runtime supports the APIs the compiler emits for the expression.
When the target is a custom collection type, the compiler requires that the type supports collection expressions through a collection builder or an applicable collection-initializer pattern. If the type does not support collection expressions, the expression fails to compile.