C# – How the Null Conditional Operator works with Nullable types

The short answer:

The null conditional operator also unwraps the nullable variable. So after the operator, the “Value” property is no longer needed.

ex:

DateTimeOffset? startAt;
...
System.Writeline(startAt?.TimeOfDay);

The longer story:

I had a scenario with a Nullable DateTimeOffset and I was trying to perform something like the following.

DateTimeOffset? startAt;
...
public DateTime? StartDate { get { return startAt.Value.DateTime; } }

As I tested, I immediately ran into a situation where that date was null, the program threw an exception and I needed to null check it. So I thought I wanted to use that latest, hottest stuff that Microsoft had to offer in C#.

The null conditional operator. 

I changed my code to the following

public DateTime? StartDate { get { return startAt?.Value.DateTime; } }

But instead it just got angry with me and for whatever reason I wasn’t looking at the obvious clues. I immediately ran off to search the interwebs on how to use this operator with Nullable types and found a solution that said to use

public DateTime? StartDate { get { return startAt.GetValueOrDefault().DateTime; } }

However, that wouldn’t give me the desired results I wanted. I want a null value back, if my original value was null. It would instead give me the default value of a DateTime. So off I went to go back to old school methods of making it happen and I change the code to look something like this

public DateTime? StartDate { get { return (startAt == null) ? null : startAt.Value.DateTime; } }

However, the compiler was still angry, giving me the error “Type of conditional expression cannot be determined because there is no implicit conversion between ‘<null>’ and ‘DateTime’. Which further confused me because when I then broke it down to basic techniques

public DateTime? StartDate { get { if (startAt == null) { return null; } else { return startAt.Value.DateTime; } } }

This worked. So I reset my frame of mind, performed a few more searches with newer clues and I discovered that to make the conditional operator work I had to provide an explicit conversion on the null return value to make it look like the following

public DateTime? StartDate { get { return (startAt == null) ? (DateTime?)null : startAt.Value.DateTime; } }

Then I created a few tests to break down each example and confirm my expected outcomes. When I created a simple example just to revisit using the null conditional operator one more time, I then finally noticed the output of the intellisense after typing the operator. This awesome operator also unwrapped the variable so that you could get straight to business.

I’m including the tests that I wrote to somewhat illustrate how my mind was processing this problem as I went through it. The tests were really just my scratch pad, for anyone who’s concerned about the best ways of writing unit tests.

Bonne nuit!

using System;
using Xunit;

namespace Test.Stuff
{
    public class Nullables
    {
        [Fact]
        public void Test_ConditionalOperator_NullValue()
        {
            DateTimeOffset? foo = null;

            var result = (foo == null) ? (DateTime?)null : foo.Value.Date;
            Assert.Null(result);
        }

        [Fact]
        public void Test_IfStatements_NullValue()
        {
            DateTimeOffset? foo = null;
            DateTime? result = null;

            if (foo == null) { result = null; } else { result = foo.Value.Date; }
            Assert.Null(result);
        }

        [Fact]
        public void Test_NullConditional_NullValue()
        {
            DateTimeOffset? foo = null;
            DateTime? result = null;

            result = foo?.Date;
            Assert.Null(result);
        }

        [Fact]
        public void Test_ConditionalOperator_WithValue()
        {
            DateTimeOffset? foo = new DateTimeOffset(2016, 09, 11, 01, 49, 00, TimeSpan.Zero);

            var result = (foo == null) ? (DateTime?)null : foo.Value.Date;
            Assert.Equal(2016, result.Value.Year);
            Assert.Equal(09, result.Value.Month);
            Assert.Equal(11, result.Value.Day);
            Assert.Equal(0, result.Value.Hour);
            Assert.Equal(0, result.Value.Minute);
        }

        [Fact]
        public void Test_IfStatements_WithValue()
        {
            DateTimeOffset? foo = new DateTimeOffset(2016, 09, 11, 01, 49, 00, TimeSpan.Zero);
            DateTime? result = null;

            if (foo == null) { result = null; } else { result = foo.Value.Date; }
            Assert.Equal(2016, result.Value.Year);
            Assert.Equal(09, result.Value.Month);
            Assert.Equal(11, result.Value.Day);
            Assert.Equal(0, result.Value.Hour);
            Assert.Equal(0, result.Value.Minute);
        }

        [Fact]
        public void Test_NullConditional_WithValue()
        {
            DateTimeOffset? foo = new DateTimeOffset(2016, 09, 11, 01, 49, 00, TimeSpan.Zero);
            DateTime? result = null;

            var blah = foo?.DateTime;

            result = foo?.Date;
            Assert.Equal(2016, result?.Year);
            Assert.Equal(09, result?.Month);
            Assert.Equal(11, result?.Day);
            Assert.Equal(0, result?.Hour);
            Assert.Equal(0, result?.Minute);
        }
    }
}

Leave a Reply