Collection expressions with arguments in C# 15
Collection expressions were introduced in C# 12. I wrote a blog post about them at that time. In C# 15, the syntax is being extended with the ability to provide additional arguments to the underlying collection constructor or builder.
Let's start with a quick reminder, what collection expressions are. Since the early days of C#, collection types could be initialized using the collection initializer syntax. For example, an array could be initialized with:
var oldSyntax = new[] { 1, 2, 3 };
Collection expressions provide an alternative syntax (notice how the variable has to be explicitly typed so that the compiler knows which type of collection to instantiate):
int[] newSyntax = [1, 2, 3];
The new syntax also added support for the spread element to initialize a new collection as a concatenation of elements from multiple existing collections, inspired by the Javascript spread syntax:
int[] array = [1, 2];
Span<int> span = [3, 4];
List<int> list = [5, 6];
int[] finalArray = [.. array, .. span, .. list];
However, the new collection expression wasn't a full replacement for all use cases. Some collection constructors have parameters which tweak how the collection gets created. For example, a list can be created with a larger initial size, which is useful if we know in advance that more items will be added to it:
var oldSyntax = new List<int>(10) { 1, 2, 3 };
Before C# 15, the same final result couldn't be achieved using collection expression syntax. The addition of so-called collection expression arguments in C# 15 makes it possible to specify values for the collection constructor parameters. The following syntax is equivalent to the one above:
List<int> newSyntax = [with(10), 1, 2, 3];
The constructor parameters are specified using the with keyword which has to be the first element in the collection expression. Its parameters must match the parameters of a constructor of the collection type being initialized.
Collection initializers can't be used with all collections. Immutable collections are such an example. The closest alternative without using collection expressions are the builder methods with params arguments. This means that the following call invokes the Create<string>(params ReadOnlySpan<string> items) method (and called Create<string>(params string[] items) before parameter collections from C# 13 added params support to other types of collections):
var oldSyntax = ImmutableHashSet.Create("a", "b", "c");
Collection expression can invoke the same method using the syntax identical to other types of collections which support collection initializers:
ImmutableHashSet<string> newSyntax = ["a", "b", "c"];
With the addition of collection expression arguments in C# 15, collection builders with other arguments in addition to the span or array for specifying the elements to be added to the collection can now supported by the collection expression syntax. For example:
ImmutableHashSet<string> newSyntax =
[
with(StringComparer.OrdinalIgnoreCase),
"a",
"A"
];
This new syntax is equivalent to the old one without collection expressions:
var oldSyntax = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, "a", "A");
As you can deduce from the given example, in the case of builder methods the collection expression arguments map to the builder method parameters placed before the span items parameter.
Just like all other C# 15 features, collection expression arguments are still in preview and might change before the November release. To use them in .NET 11 preview 7 you have to set the language version to preview:
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
I created a sample project with all the code from this blog post and pushed it to my GitHub repository. It's an easy way to try out this feature yourself.
Collection expression arguments are in essence only syntactic sugar. But so were collection expressions themselves and I still see them used quite regularly nowadays. WIth this new addition, collection expressions can now be used in a few more cases where they couldn't be before.
