Nullable Performance [Kit George, David Gutierrez]

There’s been a number of questions about Nullable performance: is doing a HasValue check performant? Here's some code which we think better mimics what your old code (scenario 1 and 2) would have looked like, vs. what we suggest you do now. Assuming in the first scenario that you had a boxed DateTime, you had to be doing a null check before casting, so we've included that into the test. The second scenario is the same, but the object is null (equivalent to HasValue=false for a Nullable type). The next test LOOKS like it has the same kind of structure, but importantly, the reference to the Value property actually does a HasValue check itself, and throws if there is no value. This means you're doing your own check for HasValue, and then it's doing a second check, so there's a perf degrade for the duplication. The last two scenarios show the best approach: do your HasValue check, and then use GetValueOrDefault. This has no HasValue check inside it, so it's performance is far better. You'll see only a slight degrade for that scenario, so things look good.

Notice in the third to last scenario, that we don't EVER do a HasValue check: and notice the great performance of this scenario. This of course only makes sense if you want to return either the value stored in the nullable, or some default (0) value otherwise. But if that IS your scenario, this should be preferred.

You’ll observe this relationship of numbers:

Result: 21285235 DateTime cast
Result: 5044263 DateTime cast, but object is null
Result: 23717132 HasValue and Value
Result: 6197101 HasValue and Value on null DateTime?
Result: 18287806 Get Value, no check
Result: 17415599 GetValueOrDefault()
Result: 20425979 HasValue and GetValueOrDefault

Note that the last option, HasValue and Get ValueOrDefault is if anything, a little faster than the cast. So overall, Nullable is as performant as it’s counterpart, and even better!

using System;

using System.Diagnostics;

public class Test

{

public static void Main()

{

// Run the test 3 times, to ensure no first run perf issues

PerfTestNullable();

PerfTestNullable();

PerfTestNullable();

}

private static void PerfTestNullable()

{

DateTime dateTime1 = DateTime.Now;

Nullable<DateTime> dateTime2 = dateTime1;

Nullable<DateTime> dateTime3 = null;

object obj = dateTime1;

DateTime result;

Stopwatch sw = new Stopwatch();

// Test normal DateTime cast

sw.Start();

for (int i = 0; i < 1000000; i++)

{

if (obj != null)

{

result = (DateTime)obj;

}

}

sw.Stop();

Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t DateTime cast");

// Test normal DateTime cast.

sw.Reset();

sw.Start();

obj = null;

for (int i = 0; i < 1000000; i++)

{

if (obj != null)

{

result = (DateTime)obj;

}

}

sw.Stop();

Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t DateTime cast, but object is null");

// Test generics cast, HasValue and assignment

sw.Reset();

sw.Start();

for (int i = 0; i < 1000000; i++)

{

if (dateTime2.HasValue)

{

result = dateTime2.Value;

}

}

sw.Stop();

Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t HasValue and Value");

// Test generics cast and no value (no assignment)

sw.Reset();

sw.Start();

for (int i = 0; i < 1000000; i++)

{

if (dateTime3.HasValue)

{

result = dateTime3.Value;

}

}

sw.Stop();

Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t HasValue and Value on null DateTime?");

// Test getting value with no check

sw.Reset();

sw.Start();

for (int i = 0; i < 1000000; i++)

{

result = dateTime2.Value;

}

sw.Stop();

Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t Get Value, no check");

// Test GetValueOrDefault

sw.Reset();

sw.Start();

for (int i = 0; i < 1000000; i++)

{

result = dateTime2.GetValueOrDefault();

}

sw.Stop();

Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t GetValueOrDefault()");

// Test GetValueOrDefault with HasValue check

sw.Reset();

sw.Start();

for (int i = 0; i < 1000000; i++)

{

if (dateTime2.HasValue)

{

result = dateTime2.GetValueOrDefault();

}

}

sw.Stop();

Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t HasValue and GetValueOrDefault");

Console.WriteLine();

}

}