I earlier wrote about Handling null checks efficiently by using extension methods to make our code more terse, not cluttered by null check blocks. When we use nullables (Nullable<T>
), we come across a similar construction in our code. We have to check if the nullable has a value before accessing the value. If we try to reference the value of a nullable, and it has no value, we get an InvalidOperationException
. This is quite similar to getting an NullReferenceException
if trying to access a reference which is null.
Let’s take the example code from my previous post, and add an enum for Nationality. Then, we add a Citizenship
property with type Nullable<Nationality>
to the Person
class. Like so:
enum Nationality
{
No,Se,Dk,Uk,Us
}
internal class Person
{
public string Name { get; set; }
public Address Address { get; set; }
public Nationality? Citizenship { get; set; }
}
If we then create a Person
object that has the Citizenship
property not set, we get an exception if we try to dereference it:
var person = new Person {Name = "Jackie Chiles"};
Console.Write("Citizenship:");
Console.Write(person.Citizenship.Value);
What we can do here, is that we can create an .IfHasValue
and .DoIfHasValue
extension methods that are analogous to the .IfNotNull
and .DoIfNotNull
extension methods in the previous blog post:
public static class FlowControlExtensions
{
[DebuggerStepThrough]
public static void DoIfHasValue<T>(this T? obj,
Action<T> action, bool doContinue = true) where T : struct
{
if (obj.HasValue)
{
action(obj.Value);
}
if (doContinue)
return;
ThrowInvalidOperationException<T, T>();
}
[DebuggerStepThrough]
public static TResult IfHasValue<T, TResult>(this T? obj,
Func<T, TResult> func, bool doContinue = true,
TResult defaultValue = default(TResult)) where T : struct
{
if (obj.HasValue)
{
return func(obj.Value);
}
return doContinue ? defaultValue :
ThrowInvalidOperationException<T, TResult>();
}
[DebuggerStepThrough]
private static TResult ThrowInvalidOperationException<T, TResult>()
{
throw new InvalidOperationException(string.Format(
"Tried to access value of nullable of {0}, but it had no value",
typeof(T).FullName));
}
}
Now we can use this to prevent our code from throwing an exception, and return a default value instead:
var person = new Person {Name = "Jackie Chiles" };
Console.Write("Citizenship:");
Console.Write(person.Citizenship.IfHasValue(c => c.ToString()));
If we wish to give it another default value, we can simply do this like so:
Console.Write(person.Citizenship
.IfHasValue(c => c.ToString(), defaultValue: "(unknown)"));
You can find the code on GitHub.