Collection Expressions: Simplifying collection creation in C#

With C# 12, collection creation in C# has been significantly simplified with the introduction of collection expressions. This new feature provides a more concise and intuitive syntax for creating and manipulating collections, making the code more readable and easier to maintain. In this article, we will explore how collection expressions work.

What are Collection Expressions?

Collection expressions allow you to create common collections using a new compact syntax. With this syntax, you can create arrays, Span, List, and types that support collection initializers. Additionally, the spread operator (..) is introduced to incorporate elements from other collections.

Changes in C# 12

Let’s explore the changes introduced by collection expressions in C# 12, focusing on how the new syntax improves collection creation and manipulation.

Creating an Array

Before: Creating arrays required using the new constructor and curly braces {} to initialize the array. This resulted in a longer and more verbose syntax:

				
					int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
				
			

After: With C# 12, you can use a more direct syntax with square brackets [] to initialize arrays. This reduces verbosity and makes the code cleaner and easier to read, eliminating the need for new and curly braces {}.

				
					int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8];

				
			

Note that when using the new array initialization syntax with square brackets [], the variable was not declared as var. This is because var is not compatible with this new initialization expression. var requires the compiler to infer the type of the variable based on the expression on the right, and the new initialization syntax does not support dynamic type inference.

				
					// This will generate a compilation error.
var numbers = [1, 2, 3, 4, 5, 6, 7, 8];

				
			

Therefore, when using this new syntax, you must explicitly specify the type of the variable.

Createing a List

Before: To create a List<T>, you had to use the new List<T> constructor and pass a collection within curly braces {}. This approach could be less intuitive and more prone to errors.

				
					List<string> numbers = new List<string> { "one", "two", "three" };
				
			

After: The new syntax with square brackets for lists is more concise and intuitive. Now you can initialize a List<T> directly with square brackets, making the code more compact and easier to write.

				
					List<string> numbers  = ["one", "two", "three"];
				
			

Creating a Span

Before: To create a Span<T>, it was necessary to use stackalloc and curly braces {}. This involved a more complex and less direct syntax.

				
					Span<char> letters = stackalloc char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'h', 'i' };
				
			

After: C# 12 simplifies the creation of Span<T> with a new syntax that uses only square brackets []. This makes the code cleaner and the creation of Span<T> more intuitive.

				
					Span<char> letters  = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
				
			

Creating a bidimensional array

Before: Creating irregular multidimensional arrays (or jagged arrays) involved using the new int[][] constructor and nested curly braces. This could result in more detailed and less readable syntax.

				
					int[][] matrix = new int[][]
{
    new int[] { 1, 2, 3 },
    new int[] { 4, 5, 6 },
    new int[] { 7, 8, 9 }
};

				
			

After: With the new syntax, initializing irregular 2D arrays is more direct and readable. Using nested square brackets to create irregular 2D arrays simplifies the code and improves clarity.

				
					int[][] matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

				
			

This new syntax also applies when using variables to initialize multidimensional arrays.

Before:

				
					int[] row0 = new int[] { 1, 2, 3 };
int[] row1 = new int[] { 4, 5, 6 };
int[] row2 = new int[] { 7, 8, 9 };
int[][] twoDFromVariables = new int[][] { row0, row1, row2 };

				
			

After:

				
					int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[][] twoDFromVariables = [row0, row1, row2];

				
			

Instead of using the new constructor to create arrays and arrays of arrays, you can now use the new square bracket syntax, which simplifies initialization and makes the code more concise.

Spread operator

The spread operator (..) is a feature that allows unpacking elements from one collection and incorporating them into another. This operator is useful for combining multiple collections into a single collection.

Here’s an example of using the spread operator to create a single array from multiple arrays:

				
					int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [..row0, ..row1, ..row2];

foreach (var element in single)
{
    Console.Write($"{element}, ");
}
// Output:
// 1, 2, 3, 4, 5, 6, 7, 8, 9,

				
			

In the example above, row0, row1, and row2 are unpacked, and their elements are incorporated into a new array single. The spread operator (..) extracts the elements from each array and combines them into a single sequence. This is especially useful when you want to consolidate data from multiple sources or create collections dynamically and fluidly. The result is that the single array will contain the values 1, 2, 3, 4, 5, 6, 7, 8, 9.

Conclusion

As expressões de coleção no C# 12 introduzem uma maneira mais concisa e legível para inicializar coleções. Com a nova sintaxe, você pode criar arrays, listas e outros tipos de coleções de maneira mais direta e menos propensa a erros. O operador spread (..) também oferece uma forma poderosa de combinar coleções, facilitando operações como a concatenação de arrays. Essas melhorias não só simplificam o código, mas também ajudam a manter a consistência e clareza em projetos C#. Para mais detalhes, você pode consultar a documentação oficial do C# 12.