C# collection expressions made collection creation much cleaner by allowing developers to use a consistent syntax such as:

int[] numbers = [1, 2, 3, 4];

However, there was a limitation.

Sometimes the collection itself needs additional configuration when it is created.

For example, a List<T> may benefit from an initial capacity, while a HashSet<T> may need a specific comparer.

Previously, developers generally had to fall back to an explicit constructor:

var names = new List<string>(100);

C# 15 introduces collection expression arguments, which allow constructor or factory arguments to be supplied directly inside a collection expression using with(...). The with(...) element must appear first in the collection expression.

This gives developers a way to keep the concise collection-expression syntax while still controlling how the collection is constructed.

What Are Collection Expression Arguments?

A normal collection expression looks like this:

List<string> names = ["John", "Maria", "David"];

With C# 15, you can provide arguments used to construct the target collection:

List<string> names =
[
    with(capacity: 100),
    "John",
    "Maria",
    "David"
];

The with(...) element supplies arguments to the constructor or collection factory used for the target type.

The important rule is that with(...) must be the first element in the collection expression.

List<string> names =
[
    with(capacity: 100),
    "John",
    "Maria"
];

This is valid.

Putting it after collection elements is not:

List<string> names =
[
    "John",
    with(capacity: 100),
    "Maria"
];

Why Was This Feature Added?

Collection expressions are intentionally concise:

var numbers = [1, 2, 3, 4];

But collection types can have important construction parameters.

Consider List<T>.

It has constructors that can specify capacity:

var names = new List<string>(1000);

The capacity can be useful when the application already knows approximately how many items will be added.

Without collection expression arguments, developers have to choose between concise collection syntax and explicit constructor syntax.

C# 15 combines the two ideas:

List<string> names =
[
    with(capacity: 1000),
    "John",
    "Maria",
    "David"
];

The collection remains visually recognizable as a collection expression while the construction behavior is explicit.

A Simple List Example

Consider an application that imports customer records.

var customers =
    new List<Customer>(1000);

The initial capacity is intentionally specified because the application expects many records.

Using a collection expression with C# 15:

List<Customer> customers =
[
    with(capacity: 1000),
    new Customer(1, "John"),
    new Customer(2, "Maria"),
    new Customer(3, "David")
];

The important distinction is that capacity is not an element in the list.

It is an argument used when constructing the collection.

Conceptually:

Collection Expression
        |
        +-- Construction arguments
        |      |
        |      +-- capacity: 1000
        |
        +-- Elements
               |
               +-- Customer 1
               +-- Customer 2
               +-- Customer 3

How the Constructor Is Selected

The compiler uses overload resolution to select an appropriate constructor based on the arguments supplied through with(...). For a target class or struct that implements IEnumerable, the arguments can be passed to an applicable constructor.

For example, List<T> provides constructors including:

List<T>()
List<T>(int capacity)
List<T>(IEnumerable<T> collection)

Therefore:

List<int> numbers =
[
    with(capacity: 10),
    1,
    2,
    3
];

can use the capacity constructor.

The important part is that the compiler does not interpret:

with(capacity: 10)

as an item being inserted into the collection.

It is part of collection construction.

HashSet and Comparers

One of the more practical examples is HashSet<T>.

A HashSet<string> can use a custom equality comparer.

Traditionally:

var names = new HashSet<string>(
    StringComparer.OrdinalIgnoreCase);

names.Add("John");
names.Add("JOHN");
names.Add("john");

The comparer determines that these strings are equal.

With C# 15 collection expression arguments:

HashSet<string> names =
[
    with(StringComparer.OrdinalIgnoreCase),
    "John",
    "JOHN",
    "john"
];

The resulting set contains one logical value because the comparer treats the strings as equal. Microsoft uses this type of example in the C# 15 documentation.

This is a particularly useful scenario because the comparer is part of the collection's behavior.

Why the Comparer Matters

Consider this code:

var users =
    new HashSet<string>(
        StringComparer.OrdinalIgnoreCase);

Now:

users.Add("Admin");
users.Add("admin");

Only one value is retained.

Without the comparer:

var users =
    new HashSet<string>();

users.Add("Admin");
users.Add("admin");

both values can exist because the default string comparison is different.

With collection expression arguments:

HashSet<string> users =
[
    with(StringComparer.OrdinalIgnoreCase),
    "Admin",
    "admin"
];

the comparison behavior is visible directly at the point where the collection is created.

Using Spread Elements

Collection expressions can also use the spread operator .. to include values from another collection.

For example:

string[] values =
[
    "one",
    "two",
    "three"
];

List<string> names =
[
    with(capacity: values.Length * 2),
    ..values
];

Here:

with(capacity: values.Length * 2)

controls construction.

And:

..values

adds the elements from the existing collection.

This combination is one of the practical reasons collection expression arguments are useful.

Microsoft's documentation provides a similar example using a calculated capacity and a spread element.

A Practical ASP.NET Core Example

Consider an ASP.NET Core application that builds a list of response objects.

Suppose the application receives a batch of customer records:

public record Customer(
    int Id,
    string Name);

The application needs to transform them into API results.

A conventional approach might be:

var results =
    new List<CustomerResponse>(
        customers.Count);

foreach (var customer in customers)
{
    results.Add(
        new CustomerResponse(
            customer.Id,
            customer.Name));
}

For a known collection, collection expressions can make the creation syntax more compact.

For example:

List<CustomerResponse> results =
[
    with(capacity: customers.Count),
    .. customers.Select(customer =>
        new CustomerResponse(
            customer.Id,
            customer.Name))
];

The capacity is based on the expected number of items.

The spread element then adds the generated responses.

Why Capacity Can Matter

When a List<T> grows beyond its current capacity, it may need to allocate a larger internal array and copy existing elements.

If the application already knows approximately how many elements it will add, setting an appropriate initial capacity can avoid unnecessary growth operations.

For example:

var results =
    new List<CustomerResponse>(
        customers.Count);

or:

List<CustomerResponse> results =
[
    with(capacity: customers.Count),
    .. customers.Select(CreateResponse)
];

This does not mean developers should specify capacity everywhere.

If the collection is small or its size is unpredictable, the added complexity may not provide meaningful value.

The important point is that C# 15 gives collection expressions access to this constructor configuration when it is useful.

Passing an Existing Collection to a Constructor

Collection expression arguments are not limited to simple scalar values.

The feature specification allows constructor arguments to participate in overload resolution, including arguments representing existing collections where the target constructor accepts them.

For example, the target type might have a constructor accepting another collection:

public sealed class CustomerCollection :
    IEnumerable<Customer>
{
    public CustomerCollection(
        IEnumerable<Customer> customers)
    {
        // Store or process customers.
    }

    public IEnumerator<Customer> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

A collection expression can provide the constructor argument through with(...) where the target type and applicable collection-expression conversion support it.

The exact constructor chosen is determined by overload resolution.

Collection Builders

Collection expressions can also target types that use a collection builder.

A collection builder is a mechanism that allows a custom collection type to define how a collection expression creates its instance.

Consider:

[CollectionBuilder(
    typeof(CustomerCollectionBuilder),
    nameof(CustomerCollectionBuilder.Create))]
public readonly struct CustomerCollection
{
}

The builder can expose a factory method for creating the collection.

Collection expression arguments can be passed to supported factory methods before the element span argument. The feature specification defines how those arguments participate in selecting and invoking collection builders.

This means the feature is not limited to built-in collection types.

It can also support carefully designed custom collection abstractions.

A Custom Collection Example

Consider a custom collection that needs a configuration value:

public sealed class BatchOptions
{
    public int MaximumItems { get; }

    public BatchOptions(int maximumItems)
    {
        MaximumItems = maximumItems;
    }
}

A custom collection could expose a construction mechanism that accepts options along with the elements.

Conceptually, the collection expression can then provide:

var batch =
[
    with(new BatchOptions(100)),
    item1,
    item2,
    item3
];

The exact support depends on the collection's construction or builder pattern.

This is where collection expression arguments become more than a convenience feature for List<T> and HashSet<T>.

Collection Expression Arguments vs Traditional Constructors

Consider the two approaches.

Traditional Constructor

var names =
    new List<string>(100);

names.Add("John");
names.Add("Maria");
names.Add("David");

Collection Expression

List<string> names =
[
    with(capacity: 100),
    "John",
    "Maria",
    "David"
];

Area

Constructor + Add

Collection Expression Arguments

Construction options

Explicit

Inside with(...)

Initial values

Added separately

Listed directly

Readability

More verbose

More compact

Collection shape

Clear

Clear

Capacity configuration

Supported

Supported

Works with builders

Depends on API

Supported where builder is compatible

The second approach is especially attractive when the collection's contents are already known at the point of creation.

Collection Expression Arguments vs Collection Initializers

Collection initializers and collection expressions are related but different features.

A collection initializer might look like:

var names =
    new List<string>
    {
        "John",
        "Maria",
        "David"
    };

A collection expression looks like:

List<string> names =
[
    "John",
    "Maria",
    "David"
];

With C# 15:

List<string> names =
[
    with(capacity: 100),
    "John",
    "Maria",
    "David"
];

The third version combines collection expression syntax with construction arguments.

This gives developers a consistent way to express both the collection's contents and important construction configuration.

Using Collection Expressions With Dictionaries

Dictionary scenarios are particularly interesting because key comparison behavior can be important.

Suppose an application uses case-insensitive configuration keys.

A traditional dictionary might be created like this:

var settings =
    new Dictionary<string, string>(
        StringComparer.OrdinalIgnoreCase)
    {
        ["Environment"] = "Production",
        ["Region"] = "India"
    };

Collection expressions already provide dictionary initialization syntax in modern C#.

With collection expression arguments, the comparer can be supplied as part of the collection expression:

Dictionary<string, string> settings =
[
    with(StringComparer.OrdinalIgnoreCase),
    ["Environment"] = "Production",
    ["Region"] = "India"
];

The important idea is that the comparer belongs to the collection's construction semantics.

It is therefore useful to keep it next to the collection declaration.

Case Sensitivity in Real Applications

Consider configuration keys:

ConnectionString
connectionstring
CONNECTIONSTRING

If an application intends these keys to be treated as equivalent, the comparer needs to be configured consistently.

For example:

Dictionary<string, string> settings =
[
    with(StringComparer.OrdinalIgnoreCase),
    ["ConnectionString"] = "...",
    ["Region"] = "India"
];

Now lookup behavior is determined by the configured comparer.

var connectionString =
    settings["connectionstring"];

The collection's behavior is immediately visible from its declaration.

with(...) Must Come First

One of the most important syntax rules is that the with(...) element must be the first element in the collection expression.

Correct:

List<int> numbers =
[
    with(capacity: 20),
    10,
    20,
    30
];

Incorrect:

List<int> numbers =
[
    10,
    with(capacity: 20),
    20
];

This rule keeps construction configuration separate from the elements being added.

It also makes it easy for someone reading a long collection expression to identify how the collection is constructed.

Argument Evaluation

Collection expression elements and collection arguments follow defined evaluation rules.

The arguments in with(...) are evaluated in order, and collection elements are also evaluated in order. The feature specification defines these evaluation rules so that the construction process remains predictable.

For example:

List<int> values =
[
    with(capacity: CalculateCapacity()),
    GetValue(1),
    GetValue(2)
];

The methods involved should be treated as normal expressions with their own side effects and evaluation order.

As a general best practice, keep constructor arguments simple.

Prefer:

List<int> values =
[
    with(capacity: expectedCount),
    10,
    20,
    30
];

over embedding complex logic directly inside the collection declaration.

Production Example: Batch Processing

Suppose an application processes orders in batches.

public record Order(
    int Id,
    decimal Amount);

The application receives a source collection:

IEnumerable<Order> orders = GetOrders();

If the number of orders is known:

var orderArray = orders.ToArray();

List<Order> batch =
[
    with(capacity: orderArray.Length),
    .. orderArray
];

This clearly communicates that the list is expected to contain the source collection's elements and that its capacity is intentionally configured.

The same approach can be useful in data transformation code where collection sizes are known before materialization.

Production Example: Case-Insensitive Tags

Suppose an application collects tags from multiple sources.

string[] incomingTags =
[
    "CSharp",
    "csharp",
    "ASP.NET",
    "asp.net"
];

Create a case-insensitive set:

HashSet<string> tags =
[
    with(StringComparer.OrdinalIgnoreCase),
    .. incomingTags
];

Now:

tags.Contains("CSharp");
tags.Contains("csharp");

both use the same equality semantics.

This is a useful production pattern because the comparer is not hidden elsewhere in the application.

Common Mistakes

Treating with(...) as a Collection Element

This:

[
    with(capacity: 100),
    1,
    2
]

does not add a with object to the collection.

It supplies construction arguments.

Placing with(...) After Elements

The with(...) element must appear first.

Using Capacity Without a Reason

Do not specify arbitrary capacities everywhere.

If there is no meaningful expected size, the default collection behavior may be sufficient.

Assuming Every Collection Supports Every Argument

The supplied arguments must match an appropriate constructor or collection builder.

Ignoring Comparer Semantics

A custom comparer can fundamentally change how dictionaries and sets behave.

Choose it intentionally.

Overcomplicating Collection Expressions

This:

var values =
[
    with(capacity: CalculateComplexCapacity()),
    GetFirstValue(),
    Transform(
        GetSecondValue(),
        GetThirdValue())
];

may be technically valid but difficult to maintain.

Collection expressions should improve readability, not become miniature programs.

Troubleshooting

The Compiler Says with(...) Is Not Supported

Collection expression arguments are a C# 15 feature. Microsoft currently documents C# 15 as a preview release supported by the .NET 11 preview SDK and Visual Studio 2026 Insiders.

Make sure the project is using an appropriate SDK and language version.

For a preview environment:

<PropertyGroup>
    <TargetFramework>net11.0</TargetFramework>
    <LangVersion>preview</LangVersion>
</PropertyGroup>

The Constructor Cannot Be Selected

Check the argument types.

For example:

List<int> values =
[
    with("100"),
    1,
    2
];

does not provide an appropriate List<int> constructor argument.

Use:

List<int> values =
[
    with(capacity: 100),
    1,
    2
];

with(...) Is Not the First Element

Move it to the beginning:

List<int> values =
[
    with(capacity: 100),
    1,
    2
];

A Custom Collection Does Not Work

Check whether the target type has an applicable constructor or collection builder that supports the supplied arguments.

Collection builder methods have specific requirements, including appropriate accessibility, generic arity, and a final ReadOnlySpan<T> parameter for the element data.

A Note About the with Name

There is an important language-version consideration.

In C# 15, with(...) at the beginning of a collection expression is interpreted as collection construction arguments rather than a normal method invocation. Microsoft documents this as a compiler behavior change for C# 15.

For example:

items = [with(x, y), z];

may have different interpretation depending on the language version and target type.

If code previously relied on a method literally named with, the C# 15 syntax can affect how that expression is parsed or bound.

The explicit escaped form can be used when the intent is to invoke a method named with:

items = [@with(x, y), z];

This is mainly relevant to migration scenarios and unusual existing code.

Best Practices

Keep Construction Arguments Meaningful

Use with(...) when the constructor argument has a real purpose.

Good:

HashSet<string> values =
[
    with(StringComparer.OrdinalIgnoreCase),
    .. input
];

Use Capacity When the Size Is Predictable

Good:

List<Order> orders =
[
    with(capacity: expectedCount),
    .. source
];

Avoid arbitrary numbers:

List<Order> orders =
[
    with(capacity: 100000),
    .. source
];

unless there is a reason for that value.

Keep Comparers Explicit

For dictionaries and sets, a comparer can affect application behavior. Keeping it in the collection declaration can improve maintainability.

Keep with(...) First

This is required by the language and makes the construction configuration easy to find.

Prefer Simple Arguments

Do not move complicated business logic into a collection declaration.

Test Custom Collections

If your application uses custom collection builders, test both normal collection creation and construction arguments.

Advantages

Concise Syntax

Collection construction and initial values can be expressed together.

Constructor Configuration

Capacity, comparers, and supported constructor arguments can be specified directly.

Better Readability

Important collection behavior can remain next to the collection declaration.

Works With Spread Elements

You can combine construction arguments with .. to populate a collection from existing values.

Supports Custom Collection Patterns

Collection builders can participate in collection expression construction where the required pattern is implemented.

Disadvantages

C# 15 Dependency

The feature requires C# 15 support and is currently part of the preview language experience.

New Syntax to Learn

Developers need to understand what with(...) means when it appears as the first element of a collection expression.

Constructor Behavior Is Less Visible

With traditional syntax:

new List<string>(100)

the constructor call is explicit.

With:

[
    with(capacity: 100),
    "A",
    "B"
]

developers need to understand collection expression semantics.

Not Every Collection Supports Arbitrary Arguments

The target type still needs an appropriate constructor or collection-builder implementation.

Migration Requires Attention

Existing code involving a method named with may need review when moving to C# 15 semantics.

When Should You Use Collection Expression Arguments?

The feature is most useful when three conditions are true:

  1. You want collection-expression syntax.

  2. The target collection needs construction configuration.

  3. That configuration has clear value to the application.

For example:

HashSet<string> permissions =
[
    with(StringComparer.OrdinalIgnoreCase),
    "Read",
    "Write",
    "Delete"
];

This is a strong use case because the comparer directly affects the collection's behavior.

Another example:

List<Customer> customers =
[
    with(capacity: expectedCustomerCount),
    .. sourceCustomers
];

This can be useful when the expected collection size is already known.

When Should You Avoid It?

Avoid collection expression arguments when the construction configuration adds no meaningful value.

Instead of:

List<int> values =
[
    with(capacity: 3),
    1,
    2,
    3
];

you may simply write:

List<int> values =
[
    1,
    2,
    3
];

The default capacity behavior is perfectly reasonable for many small collections.

Also avoid using the feature simply because it is new.

The purpose of the feature is to make collection construction more expressive, not to replace every existing collection declaration.

Practical Comparison

Scenario

Recommended Approach

Small collection with no configuration

Normal collection expression

Known large collection size

Collection expression with with(capacity: ...)

Case-insensitive HashSet

Collection expression with comparer

Case-insensitive dictionary

Collection expression with comparer

Complex construction logic

Explicit constructor or factory

Database query

Explicit method or query

Remote API call

Explicit asynchronous method

Custom collection

Collection expression if builder supports it

Unclear construction semantics

Prefer explicit construction

Summary

C# 15 collection expression arguments extend the existing collection-expression syntax with a way to control how the target collection is constructed.

The key syntax is:

[
    with(...),
    ...
]

The with(...) element must be first, and its arguments are passed to an appropriate constructor or collection factory. This allows developers to specify settings such as list capacity or set and dictionary comparers while keeping the concise collection-expression syntax.

For example:

List<string> names =
[
    with(capacity: 100),
    "John",
    "Maria",
    "David"
];

and:

HashSet<string> names =
[
    with(StringComparer.OrdinalIgnoreCase),
    "John",
    "JOHN"
];

The feature is particularly useful when construction configuration is part of the collection's behavior. It should not be used simply to make every collection declaration look newer.

For production applications, the best approach is to use collection expression arguments where they make intent clearer: capacity when collection size is predictable, comparers when equality behavior matters, and constructor or builder arguments when they represent meaningful collection configuration.