C# List Pattern Examples
We recently upgraded Abbot to .NET 7 and C# 11 and I’m just loving the new language features in C#. In this post, I’ll give a couple examples of list patterns.
Single Item List
There are cases where I expect up to one item in a list. Any more and I want to throw an exception. Here’s one way you can deal with it:
BEFORE
List<int> list = SomeMethodThatReturnsAList(someId);
var formattedItem = list.SingleOrDefault() is {} singleItem
? $"Formatted: {singleItem}"
: "No items found";
Note that if there’s no items, nothing happens. That’s fine in this case. If there’s two or more items, it throws an exception, but not exactly the most helpful one. Here’s how I might handle this with a list pattern.
AFTER
List<int> list = SomeMethodThatReturnsAList(someId);
var formattedItem = list switch {
[] => "No items found",
[var singleItem] => $"Formatted: {singleItem}",
_ => throw new InvalidOperationException($"Expected 0 or 1 items, but got {list.Count} items for Id: {someId}.")
};
This is a bit more verbose, but it’s very clear that I’m handling every possible case I care about for the list, rather than relying on the behavior of SingleOrDefault()
. Not only that, the exception I get is much more helpful.
Multiple Items
Here’s something that comes up a lot in my code. I have a formatted string I want to split into parts. Suppose it’s fine to have two or three parts, but no less and no more.
BEFORE
public record Parts(string Part1, string Part2, string? Part3 = null) {
public static Parts Parse(string formatted) {
var parts = formatted.Split('|');
return parts.Length switch {
2 => new Parts(parts[0], parts[1]),
3 => new Parts(parts[0], parts[1], parts[2]),
var length => throw new InvalidOperationException($"Expected 3 parts, but got {length} parts for formatted string: {formatted}."),
};
}
public override string ToString() => Part3 is null
? $"{Part1}|{Part2}"
: $"{Part1}|{Part2}|{Part3}";
}
AFTER
public record Parts(string Part1, string Part2, string? Part3 = null) {
public static Parts Parse(string formatted) {
return formatted.Split('|') switch {
[var part1, var part2] => new Parts(part1, part2),
[var part1, var part2, var part3] => new Parts(part1, part2, part3),
var parts => throw new InvalidOperationException($"Expected 3 parts, but got {parts.Length} parts for formatted string: {formatted}."),
};
}
public override string ToString() => Part3 is null
? $"{Part1}|{Part2}"
: $"{Part1}|{Part2}|{Part3}";
}
Conclusion
Working with lists just got easier with C# 11. It’s taking all my restraint not to go through our entire codebase and refactor all the code that would be improved with list patterns. I’m sure I’ll get there eventually.
For more on list patterns, check out the docs!
Comments
2 responses