One of the things to think about when writing LINQ filter expressions (First, Last, or Single) is what happens when the thing you are looking for was not found.

Take this code for example:

var numbers = new[] { 3, 4, 5, 6, 7, 9 };

var lowest = numbers.Single(n => n < 4);

Console.WriteLine(lowest);

This code will print ``3, which is the only number less than 4.

If we change the code like this:

var lowest = numbers.Single(n => n < 3);

Console.WriteLine(lowest);

It will crash with an InvalidOperationException at runtime, as no value satisfies the expression < 3

To mitigate against this, you can use the SingleOrDefault method.

Like this:

var lowest = numbers.SingleOfDefault(n => n < 3);

Console.WriteLine(lowest);

Instead of crashing unceremoniously, this version will print a 0 instead, as nothing matched the expression.

The 0 here is no accident - it is the default value of an integer type.

If it was an array of booleans, it would have printed false, as false is the default value of a boolean.

What if you didn’t want a 0, or 0 might be a valid output and you want something else to signal that there was no match?

.NET 6 introduces an overload where you can specify a custom default.

So our previous example becomes:

var numbers = new[] { 3, 4, 5, 6, 7, 9 };

var lowest = numbers.SingleOrDefault(n => n < 3, -1);

This now prints -1 as its output.

This is great for primitive objects, but what about for a complex objects like a class?

Let us try with this class:

public record Animal
{
    public string Name { get; init; }
    public byte Legs { get; init; }
}

If we run this code:

var animals = new Animal[] 
{
    new Animal() { Name = "Cat", Legs = 4},
    new Animal() { Name = "Dog", Legs = 4}
};

var twoLegged = animals.SingleOrDefault(n => n.Legs == 2);
Console.WriteLine(twoLegged);

This will print null, as no animal satisfies the expression Legs == 2.

Why? Because the default of a reference object is null.

However the overload still works for classes. So we can do this:

var animals = new Animal[] 
{
    new Animal() { Name = "Cat", Legs = 4},
    new Animal() { Name = "Dog", Legs = 4}
};

var twoLegged = animals.SingleOrDefault(n => n.Legs == 2, new Animal() { Name = "Bird", Legs = 2 });

Here we are creating a new Animal, a Bird, if our expression does not return any matches.

You an also provide another expression as the argument, provided that expression returns an Animal.

var twoLegged = animals.SingleOrDefault(n => n.Legs == 2, animals.First());

This will return a Cat.

What would happen if you used a struct instead of a class?

public record struct Animal
{
    public string Name { get; init; }
    public byte Legs { get; init; }
}

What would you expect this code to return, given a struct, being a value type, cannot be null?

var animals = new Animal[] 
{
    new Animal() { Name = "Cat", Legs = 4},
    new Animal() { Name = "Dog", Legs = 4}
};

var twoLegged = animals.SingleOrDefault(n => n.Legs == 2);

This will still return an Animal struct, but its values will be initialized to the defaults - Name will be null and Legs will be 0.

This new overload works with these extension methods:

It does not seem to have been implemented for ElementAtOrDefault

Thoughts

This will allow for the writing of cleaner code when handling edge cases for expressions that fail to return results.

TLDR

LINQ expressions that have the OrDefault signature now have an overload to allow you to pass a default value if the expression does not return a result.

This is Day 22 of the 30 Days Of .NET 6 where every day I will attempt to explain one new / improved thing in the upcoming release of .NET 6.