C# tutorials > Modern C# Features > C# 6.0 and Later > Explain collection expressions in C# 12 and their syntax.

Explain collection expressions in C# 12 and their syntax.

Collection expressions, introduced in C# 12, provide a concise and expressive way to create collections such as arrays, spans, and lists. They simplify collection initialization, making code cleaner and easier to read. This tutorial will explore the syntax and usage of collection expressions with practical examples.

Basic Syntax

Collection expressions use square brackets `[]` to enclose the elements of the collection. The elements are separated by commas. The compiler infers the type of the collection based on the elements within the brackets. This syntax works seamlessly with arrays, `List`, `Span`, `ReadOnlySpan`, and other collection types that support it.

// Creating an array using a collection expression
int[] numbers = [1, 2, 3, 4, 5];

// Creating a List<string> using a collection expression
List<string> names = ["Alice", "Bob", "Charlie"];

//Creating a ReadOnlySpan<int> using a collection expression
ReadOnlySpan<int> span = [10, 20, 30];

Spread Operator (..)

The spread operator `..` allows you to include all elements from an existing collection into a new collection expression. This is particularly useful for combining multiple collections into a single one. The spread operator simplifies the concatenation process, making the code more readable than traditional methods like `Concat`.

// Using the spread operator to combine collections
int[] firstHalf = [1, 2, 3];
int[] secondHalf = [4, 5, 6];
int[] combined = [..firstHalf, ..secondHalf]; // Result: [1, 2, 3, 4, 5, 6]

List<string> moreNames = ["David", "Eve"];
List<string> allNames = [..names, ..moreNames]; //Result: ["Alice", "Bob", "Charlie", "David", "Eve"]

Conditional Elements

Collection expressions can incorporate conditional elements using the ternary operator `?:`. This allows you to include or exclude elements based on a condition, making the collection initialization more dynamic and flexible. This is useful for filtering and transforming data as you create the collection.

bool includeEven = true;
int[] conditionalNumbers = [1, 2, includeEven ? 4 : 0, 5, includeEven ? 6 : 0]; // Result: [1, 2, 4, 5, 6]

string message = "Success!";
List<string> messages = ["Starting",  (message != null ? message : "No message")];

Concepts Behind the Snippet

Collection expressions are syntactic sugar provided by the C# compiler to simplify collection initialization. Under the hood, the compiler translates these expressions into equivalent code that uses constructors, `AddRange` methods, or other appropriate initialization techniques, depending on the target collection type. The goal is to improve code readability and maintainability without introducing any runtime overhead.

Real-Life Use Case Section

Imagine you are building a web API that returns a list of active users. You might fetch user data from multiple sources and combine them into a single list. Collection expressions with the spread operator can greatly simplify this process: csharp List activeUsers = [..GetUsersFromDatabase(), ..GetUsersFromCache(), ..GetUsersFromExternalService()]; Another use case is building configuration arrays based on environment variables or user settings. Conditional elements can be used to include specific configurations based on these variables.

public class User { public string Name {get; set;} }

public List<User> GetUsersFromDatabase() { return new List<User>() { new User() {Name = "Alice"}, new User() { Name = "Bob"}}; }
public List<User> GetUsersFromCache() { return new List<User>() { new User() {Name = "Charlie"}}; }
public List<User> GetUsersFromExternalService() { return new List<User>() { new User() {Name = "David"}}; }

public List<User> GetAllActiveUsers()
{
	return [..GetUsersFromDatabase(), ..GetUsersFromCache(), ..GetUsersFromExternalService()];
}

Best Practices

  • Use collection expressions for simple and straightforward collection initializations.
  • Employ the spread operator to efficiently combine existing collections.
  • Use conditional elements sparingly to avoid overly complex expressions.
  • Ensure the types of elements being combined are compatible to avoid runtime errors.
  • Keep expressions concise and readable for maintainability.

Interview Tip

When discussing collection expressions in an interview, highlight their ability to improve code clarity and reduce boilerplate. Be prepared to explain the spread operator and how it simplifies collection concatenation. Also, mention the underlying compilation process and how it translates the expression into standard C# code. Understanding the performance implications (or lack thereof) is also beneficial.

When to Use Them

Collection expressions are best suited for scenarios where you need to create collections with a known set of elements or combine existing collections in a clear and concise manner. They are particularly useful in scenarios involving data transformations, filtering, and aggregation. Avoid using them for complex collection manipulations that might make the code less readable.

Memory Footprint

Collection expressions themselves do not introduce any significant overhead in terms of memory footprint. The memory usage depends on the size and type of the collection being created. When using the spread operator, keep in mind that it creates a new collection containing all the elements from the original collections, which might require additional memory allocation. However, this is similar to using `Concat` or `AddRange`.

Alternatives

Before C# 12, collection initialization often involved using object initializers, `AddRange` methods, or array constructors. For example: csharp //Traditional way without collection expressions int[] oldNumbers = new int[] { 1, 2, 3, 4, 5 }; List oldNames = new List() { "Alice", "Bob", "Charlie" }; These approaches are still valid but are generally more verbose and less readable than collection expressions.

Pros

  • Improved Readability: Collection expressions provide a more concise and expressive syntax for collection initialization.
  • Reduced Boilerplate: They eliminate the need for explicit constructors and `AddRange` calls.
  • Simplified Concatenation: The spread operator simplifies the process of combining multiple collections.
  • Enhanced Flexibility: Conditional elements allow for dynamic collection initialization based on runtime conditions.

Cons

  • Learning Curve: Developers unfamiliar with C# 12 might require some time to adapt to the new syntax.
  • Potential for Overuse: Using collection expressions for overly complex scenarios might reduce code readability.
  • Limited Applicability: They are primarily useful for collection initialization and concatenation and not for more complex collection manipulations.

FAQ

  • Are collection expressions supported in older versions of C#?

    No, collection expressions were introduced in C# 12 and are not supported in earlier versions.
  • Can I use collection expressions with custom collection types?

    Yes, as long as your custom collection type supports an appropriate constructor or an `Add` method that the compiler can use to initialize the collection from the elements in the expression.
  • Do collection expressions introduce any performance overhead?

    No, collection expressions are a syntactic feature and do not introduce any significant runtime overhead. The compiler translates them into equivalent C# code during compilation.
  • Can I use null values within collection expressions?

    Yes, you can include `null` values as elements in a collection expression, provided that the collection type supports null values. For example, `string?[] names = ["Alice", null, "Bob"];` is valid.