C# 12 Collection Expressions and Spread Operator
C# 12 introduces collection expressions and the spread operator, delivering a unified, concise syntax for creating and manipulating collections. This feature eliminates the need for disparate initialization syntaxes across arrays, lists, spans, and immutable types. This article breaks down how collection expressions function, demonstrates the usage of the spread operator, and highlights the performance benefits these additions bring to modern .NET development.
What Are Collection Expressions?
Prior to C# 12, initializing collections required varying syntax depending on the target type:
int[] array = new int[] { 1, 2, 3 };
List<int> list = new List<int> { 1, 2, 3 };
Span<int> span = stackalloc int[] { 1, 2, 3 };Collection expressions replace these inconsistent declarations with a
single, bracketed syntax ([...]):
int[] array = [1, 2, 3];
List<int> list = [1, 2, 3];
Span<int> span = [1, 2, 3];
ReadOnlySpan<int> readOnlySpan = [1, 2, 3];
HashSet<int> set = [1, 2, 3];Collection expressions are target-typed, meaning the compiler determines the appropriate concrete type based on the context in which the expression appears. You can also pass collection expressions directly into method arguments:
void PrintValues(IEnumerable<string> items) =>
Console.WriteLine(string.Join(", ", items));
PrintValues(["apple", "banana", "cherry"]);What Is the Spread Operator?
The spread operator (..) allows you to inline or flatten
elements from existing collections directly into a new collection
expression. It acts as a cleaner, native alternative to combining
collections using methods like Concat() or
AddRange().
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
// Combines elements from both arrays into a single list
List<int> combined = [..first, ..second]; // [1, 2, 3, 4, 5, 6]
// Mixes individual elements with spread collections
int[] expanded = [0, ..first, 99, ..second, 100];
// [0, 1, 2, 3, 99, 4, 5, 6, 100]The spread operator works across mixed collection types. For
instance, you can spread an array and a List<T> into
a ReadOnlySpan<T> seamlessly:
string[] vowels = ["a", "e", "i"];
List<string> remaining = ["o", "u"];
ReadOnlySpan<string> allVowels = [..vowels, ..remaining];Compiler Optimizations and Performance
Collection expressions are not merely syntactic sugar; they are optimized by the C# compiler:
- Capacity Allocation: When using the spread operator
with collections that implement
ICollection<T>or have known lengths, the compiler computes the total required capacity in advance to avoid multiple memory reallocations. - Stack Allocation: When targeting
Span<T>orReadOnlySpan<T>, the compiler can allocate the backing storage directly on the stack or reference compiler-generated static data within the assembly's metadata, completely avoiding heap allocations.
Custom Types Support
Collection expressions are extensible. Any custom type can support
this syntax by implementing IEnumerable and an
Add method, or by decorating the type with the
[CollectionBuilder] attribute to specify a custom builder
method for initializing the collection.