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!
What's In This Article: C# Collection Initializers and Collection Expressions
Remember to check out these platforms:
// FIXME: social media icons coming back soon!
Using List<T> with Collection Initializers in C#
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
Microsoft[
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 C# 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!
Affiliations
These are products & services that I trust, use, and love. I get a kickback if you decide to use my links. There’s no pressure, but I only promote things that I like to use!
- BrandGhost: My social media content and scheduling tool that I use for ALL of my content!
- RackNerd: Cheap VPS hosting options that I love for low-resource usage!
- Contabo: Affordable VPS hosting options!
- ConvertKit: The platform I use for my newsletter!
- SparkLoop: Helps add value to my newsletter!
- Opus Clip: Tool for creating short-form videos!
- Newegg: For all sorts of computer components!
- Bulk Supplements: Huge selection of health supplements!
- Quora: I answer questions when folks request them!