I was recently inspired by some interesting performance characteristics for collection initializers and collection expressions in C#, and I wanted to get an introductory article put together. This article will be part of a small series where I first introduce you to the syntax we have to work with for both collection expressions and collection initializers in C#. We’ll see the difference in style and readability — which the dotnet team has been progressing to make the language feel less heavy-handed while still maintaining expressiveness.
Once you’ve got a feel for how collection initializers and collection expressions in C# work, you’ll be geared up to check out some of the performance benchmarks on them. There’s no point in hyper-optimizing for this stuff unless you understand the basics!
Using List<T> with Collection Initializers in CSharp
Collection initializers in C# allow for a concise and readable way to populate collections like List<T>
upon instantiation. This feature simplifies code by enabling the initialization of a collection with a set of predefined elements without the need for multiple calls to the Add
method.
These next subsections will show examples of collection initializers with the List<T>
type. Keep in mind that the entire point of these initializers is to define collections with elements in them to start with, which would save us from doing something like the following:
List<string> devLeaderCoolList = new List<string>();
devLeaderCoolList.Add("Hello");
devLeaderCoolList.Add(", ");
devLeaderCoolList.Add("World!");
So with this as a starting point, consider that the upcoming examples make this feel more streamlined and concise.
Example 1: Initializing a List of Integers
List<int> primeNumbers = new List<int> { 2, 3, 5, 7, 11, 13, 17 };
This example demonstrates initializing a List<int>
with a collection of prime numbers. The numbers are enclosed in braces {}
and separated by commas, directly following the instantiation of the list.
We can also use a slightly more short-hand syntax to drop the entire duplicated type definition from the right side of the equal sign:
List<int> primeNumbers = new() { 2, 3, 5, 7, 11, 13, 17 };
Example 2: Combining Object and Collection Initializers
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
List<Student> students = new List<Student>
{
new Student { Name = "Alice", Age = 22 },
new Student { Name = "Bob", Age = 24 }
};
Here, we combine object and collection initializers to create a List<Student>
where each Student
object is initialized with Name
and Age
properties. This approach streamlines the process of filling a collection with fully initialized objects. The important thing to pay attention to here is that the object initializer syntax is very much like the collection initializer syntax, but we are indeed doing two things:
Assigning values to properties of new objects
Assigning the collection elements upon collection creation
Example 3: Using Complex Expressions
List<double> areas = new List<double>
{
Math.PI * Math.Pow(3, 2),
Math.PI * Math.Pow(5, 2)
};
This example initializes a List<double>
with areas of circles (using πr²
), where r
is the radius. It illustrates that expressions, including method calls, can be used within collection initializers — that is, there is no restriction on constants or pre-evaluated values.
Example 4: Nested Collection Initializers
public class Classroom
{
public List<Student> Students { get; set; }
}
List<Classroom> classrooms = new List<Classroom>
{
new Classroom
{
Students = new List<Student>
{
new Student { Name = "Alice", Age = 22 }
}
},
new Classroom
{
Students = new List<Student>
{
new Student { Name = "Bob", Age = 24 }
}
}
};
This example shows how to use nested collection initializers to initialize a list of Classroom
objects, each containing a list of Student
objects. Very much like Example 2 that we looked at already, but this takes things up one more notch showing multiple collection initializers and object initialization as well.
Example 5: Simple List Initialization with Collection Expressions
In C# 11 we start to get some new fancier syntax for collection expressions. This was a step forward in reducing the verbosity of collection declarations using more shorthand similar to other languages. Microsoft says in their documentation:
You can use a collection expression to create common collection values. A collection expression is a terse syntax that, when evaluated, can be assigned to many different collection types. A collection expression contains a sequence of elements between
[
and]
brackets.
Let’s check out an example:
List<int> evenNumbers = [2, 4, 6, 8, 10];
This example demonstrates the straightforward initialization of a List<int>
using the new collection expressions syntax, making the code more concise and readable.
Example 6: Combining Collections with Spread Operator
And there are even more goodies in C# 12 — we get the spread operator for collection initialization:
List<int> firstBatch = [1, 2, 3];
List<int> combinedList = [0, ..firstBatch, 4];
Here, the spread operator ..
is used to include elements from an existing collection (firstBatch
) into a new list, showcasing the flexibility of the new syntax in combining collections seamlessly.
Where Are We Headed With These?
As you read through the various code examples, you can decide for yourself which ones offer you the readability that you prefer. There’s not necessarily a right or wrong answer here, but with variety and choice, we are likely going to need to make decisions within our teams about how to stay consistent. Try to strike a balance between minimizing redundancy without hiding too much type information!
But what’s next? Do we care THAT much about readability?
I mean, I do. Honestly, I think it’s incredibly important to prioritize readability in code. But if you’re curious like me, you might see a post like this from Dave Callan on the interwebs and get very curious:
And since this is what sparked my interest and caused me to write this article as a primer, you’ll perhaps be interested in the performance characteristics that I investigated!
Wrapping Up CSharp Collection Initializers and Collection Expressions
Now that you’ve seen various examples of the syntax that we have access to, you can make your own informed decisions about which are most readable to you. I think it’s always important to spend some time looking at alternatives so that you can understand different perspectives, even if it seems like it might be minor. Odds are you’re going to read and write collection initializers and collection expressions MANY times in your software engineering career — so why not optimize your choice?
Speaking of optimizations… wait until you see the next article on the performance of collection initializers!
If you found this useful and you’re looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube! Meet other like-minded software engineers and join my Discord community!
Want More Dev Leader Content?
- Follow along on this platform if you haven’t already!
- Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos: SUBSCRIBE FOR FREE
- Looking for courses? Check out my offerings: VIEW COURSES
- E-Books & other resources: VIEW RESOURCES
- Watch hundreds of full-length videos on my YouTube channel: VISIT CHANNEL
- Visit my website for hundreds of articles on various software engineering topics (including code snippets): VISIT WEBSITE
- Check out the repository with many code examples from my articles and videos on GitHub: VIEW REPOSITORY
Top comments (2)
Hi Dev Leader,
Your tips are very useful
Thanks for sharing
Glad you're enjoying! Thank you!!