LINQ: FirstOrDefault without the null check afterwards.

So, I was considering a problem I had that called for a LINQ statement that contains a FirstOrDefault() call. If there was an object returned I wanted a string property from it. If not, then I wanted to use a string.Empty.

I’ve often just caputured the result of the LINQ statement, checked for null, and if I had an object I’d call the property.

e.g.

var number = numbers.FirstOrDefault();
string firstNumberCaption = string.Empty;
if (number != null)
    firstNumberCaption = number.Caption;
Console.WriteLine("The first number is {0}", firstNumberCaption);

However, that code is a bit convoluted, and it occurred to me that there would be a better way of doing this.

The pure LINQ way

What if I called Select before the FirstOrDefault() to get the value of the property that I wanted. That way I don’t have to worry about implementing my check.

var firstNumber = numbers
    .Select(n => n.Caption)
    .FirstOrDefault();

And if I am desperate for the resulting string to be the empty string I can always append ?? string.Empty onto the end, like this:

var firstNumber = numbers
    .Select(n => n.Caption)
    .FirstOrDefault() ?? string.Empty;

If you are not familiar with the ?? (null-coalescing) operator you can find out more on MSDN.

Optimisation

My next concern was that perhaps it will convert every instance in the enumerable that was passed to LINQ before discarding them all but the first one. However, that doesn’t happen. For example, the following code only outputs one thing:

var firstNumber = numbers
    .Select(n =>
    {
        Console.WriteLine("Select {0} / {1}", n.Value, n.Caption);
        return n.Caption;
    })
    .FirstOrDefault() ?? string.Empty;

Filtering

I often put filters in my calls to FirstOrDefault(), and most of the time it is a filter on something other than what I’m returning in the Select part of the statement. Obviously, if we are performing the Select first, then the filter is not going to work if it is looking for data that is no longer there. In this case we just insert a Where statement just before the select to ensure that the filtering does happen.

So, for example, the first number in the sequence greater than 2:

var firstNumberCaption = numbers
    .Where(n => n.Value > 2)
    .Select(n => n.Caption)
    .FirstOrDefault() ?? string.Empty;

Console.WriteLine("The first number is {0}", firstNumberCaption);

Full program

Here is the full program (using the last example) if you want to see everything that is going on.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
    class Number
    {
        public Number(int value, string caption)
        {
            Value = value;
            Caption = caption;
        }

        public int Value { get; set; }
        public string Caption { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Number[] numbers =
                {
                    new Number(1, "One"),
                    new Number(2, "Two"),
                    new Number(3, "Three")
                };

            var firstNumberCaption = numbers
                .Where(n => n.Value > 2)
                .Select(n => n.Caption)
                .FirstOrDefault() ?? string.Empty;

            Console.WriteLine("The first number is {0}", firstNumberCaption);

            Console.ReadLine();
        }
    }
}

1 Comment

  1. Ross Dargan says:

    An interesting problem – there are some pretty cool options to fixing it to.

    I quite like the idea of using the null object pattern to solve this – (http://en.wikipedia.org/wiki/Null_Object_pattern) where caption is string.empty.

    public Class NullNumber : Number
    {
    Caption { get { return String.Empty;}}
    Value { get { return -1;}}
    }

    var firstNumberCaption = (numbers.FirstOrDefault(n>n.Value>2) ?? new NullNumber()).Caption

    So many ways to solve what appears to be a simple problem 🙂

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s