Back to Blog
C#

Using the C# Collection Expression Spread Operator

c# collection expression spread operator: Learn how the C# collection expression spread operator works, including syntax, target typing, allocation behavior, and commo...

C# 12Collection ExpressionsSpread OperatorArraysLists
A visual metaphor showing elements from multiple source collections being spread into a single target collection container.

c# collection expression spread operator requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

What the Spread Operator Does in Collection Expressions

C# 12 introduced collection expressions, and the .. spread operator lets you expand the elements of an existing collection directly into a new collection expression. Instead of copying elements with a loop or chaining LINQ methods, you write the source collection once and let the compiler handle the enumeration.

int[] first = [1, 2, 3]; int[] second = [4, 5, 6]; int[] combined = [.. first, .. second];

The resulting array contains 1, 2, 3, 4, 5, 6. The spread operator contributes each element of first and second in order, and the compiler generates the code that builds the target collection.

Basic Syntax and Minimal Examples

The spread operator appears inside a collection expression, which is the bracket syntax [...] introduced in C# 12. You can mix literal elements and spread sources in the same expression.

List<string> names = ["Alice", "Bob"]; List<string> expanded = ["Carol", .. names, "Dave"];

The order is preserved: Carol comes first, then Alice and Bob, then Dave. This makes the spread operator useful for prepending or appending elements without temporary variables.

You can also spread multiple sources of different collection types in one expression.

string[] a = ["x", "y"]; List<string> b = ["z"]; string[] result = [.. a, .. b];

The element types must be compatible with the target collection's element type. The compiler performs the usual type checking on each contributed element.

Target Typing and Collection Type Resolution

Collection expressions are target-typed. The compiler decides the concrete collection type from the context where the expression appears, not from the spread sources. The same spread expression can produce an array, a list, or a span depending on the assignment target.

int[] array = [.. source]; List<int> list = [.. source]; Span<int> span = [.. source];

The spread operator itself does not force a particular target type. It only provides the elements. This differs from calling a method that returns a fixed type, which makes collection expressions flexible when the same elements need to appear in different collection shapes.

The compiler generates different code for each target type. For an array, it allocates an array and copies elements. For a List<T>, it creates a list and adds elements. For a Span<T>, the behavior depends on the source and the runtime capabilities available.

Spreading Different Collection Types

The spread operator works with any type that can be enumerated, including arrays, lists, spans, and types implementing IEnumerable<T>. You can combine sources of different types in a single expression.

int[] left = [1, 2]; List<int> middle = [3, 4]; int[] right = [5, 6]; int[] combined = [.. left, .. middle, .. right];

The compiler enumerates each source in order and appends its elements to the target. There is no requirement that the sources share a concrete type; only the element type must be compatible.

This is a meaningful difference from array concatenation methods that require both operands to be arrays. The spread operator accepts any enumerable source, which reduces the need to convert collections before combining them.

Performance and Allocation Behavior

The spread operator is a compile-time convenience. The generated code performs real enumeration and allocation, so it is not free. When the target is an array and the sources have lengths known at compile time, the compiler can allocate the exact size. When the sources have unknown lengths, the generated code may need to grow the target dynamically.

For a List<T> target, the compiler typically relies on the list's Add method, which may reallocate the internal buffer as it grows. If you know the final size in advance and the compiler cannot determine it, a manual approach with a pre-sized list may allocate less.

None of this is a reason to avoid the spread operator in ordinary code. It is a reason to treat it like any other collection construction when it appears in a hot path. Measure the actual behavior before replacing readable code with manual loops.

Common Mistakes and Edge Cases

A null source throws at runtime. The spread operator enumerates the source, so spreading a null collection results in a NullReferenceException.

int[]? source = null; int[] result = [.. source]; // throws NullReferenceException

Guard against null sources when the collection may be absent. An empty collection is fine; a null reference is not.

The spread operator copies references, not objects. For reference types, the new collection holds the same object references as the source. Mutating an object through one collection is visible through the other. If you need independent copies of the objects themselves, you must clone them separately.

Compatibility Requirements

Collection expressions and the spread operator require C# 12 or later. The language version is the primary gate. If your project targets an older language version, the compiler rejects the bracket syntax and the spread operator.

The runtime version also matters in some cases. The generated code for certain target types may rely on APIs that are only available in recent .NET versions. For example, constructing a Span<T> from a collection expression may require runtime support that older frameworks do not provide. When in doubt, check the target framework of your project and test the generated behavior on that framework.

For projects that cannot move to C# 12, the equivalent code uses Concat, Union, or explicit loops. These approaches are more verbose but work on older language versions.

When the Spread Operator Is the Right Choice

Use the spread operator when the target type is clear from context and you want to combine collections in a single readable expression. It is well suited for building arrays, lists, and spans from a mix of literals and existing collections.

Avoid it when the construction logic is conditional in complex ways. A loop or a dedicated builder method is clearer when elements are added based on runtime conditions that do not map naturally to a collection expression. Also, if allocation behavior matters in a measured hot path, compare the spread operator against a manual implementation rather than assuming it is optimal.

c# collection expression spread operator: Practical Usage an | RYUSLOG DEV